Skip to content

Commit 9fc86cc

Browse files
authored
Merge pull request #558 from kevross33/patch-351078
Add EtherHiding smart contract call detection
2 parents a22d21b + cba08f2 commit 9fc86cc

1 file changed

Lines changed: 71 additions & 0 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Copyright (C) 2026 Kevin Ross, created with assistance from Gemini
2+
#
3+
# This program is free software: you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License as published by
5+
# the Free Software Foundation, either version 3 of the License, or
6+
# (at your option) any later version.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
16+
from lib.cuckoo.common.abstracts import Signature
17+
18+
class EtherHidingSmartContractCall(Signature):
19+
name = "etherhiding_smart_contract_call"
20+
description = "Transmitted an Ethereum JSON-RPC command over the network, indicative of retrieving a payload/C2 from a smart contract using EtherHiding"
21+
severity = 3
22+
confidence = 100
23+
categories = ["network", "c2", "defense_evasion"]
24+
authors = ["Kevin Ross"]
25+
minimum = "1.3"
26+
evented = True
27+
ttps = ["T1102", "T1568"]
28+
29+
filter_apinames = {
30+
"HttpSendRequestA", "HttpSendRequestW",
31+
"WinHttpSendRequest", "WinHttpWriteData",
32+
"InternetWriteFile", "send", "WSASend", "sendto", "WSASendTo"
33+
}
34+
35+
def __init__(self, *args, **kwargs):
36+
Signature.__init__(self, *args, **kwargs)
37+
self.ret = False
38+
self.contract_calls = set()
39+
40+
def on_call(self, call, process):
41+
api = call["api"]
42+
43+
# Dynamically grab the payload regardless of which networking API was used
44+
buffer_data = (
45+
self.get_argument(call, "buffer") or
46+
self.get_argument(call, "lpBuffer") or
47+
self.get_argument(call, "lpOptional") or
48+
self.get_argument(call, "Buffer") or
49+
self.get_argument(call, "PostData")
50+
)
51+
52+
if buffer_data and isinstance(buffer_data, str):
53+
buffer_lower = buffer_data.lower()
54+
55+
# Look for the JSON-RPC payload format
56+
if '"jsonrpc"' in buffer_lower and '"method"' in buffer_lower:
57+
if '"eth_call"' in buffer_lower or '"eth_gettransactionbyhash"' in buffer_lower or '"eth_getstorageat"' in buffer_lower:
58+
proc_name = process.get("process_name", "unknown")
59+
60+
# Truncate buffer to prevent massive strings blowing up the UI
61+
event_msg = f"{proc_name} transmitted a smart contract query via {api}: {buffer_data[:150]}..."
62+
63+
if event_msg not in self.contract_calls:
64+
self.contract_calls.add(event_msg)
65+
self.mark_call()
66+
self.ret = True
67+
68+
def on_complete(self):
69+
if self.ret:
70+
self.data.append({"etherhiding_contract_queries": list(self.contract_calls)})
71+
return self.ret

0 commit comments

Comments
 (0)