-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFlowTrace.hs
More file actions
355 lines (319 loc) · 12.3 KB
/
Copy pathFlowTrace.hs
File metadata and controls
355 lines (319 loc) · 12.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
-- NullSec FlowTrace - Network Flow Analyzer
-- Haskell security tool demonstrating:
-- - Pure functional programming
-- - Strong static typing with type classes
-- - Algebraic data types
-- - Pattern matching
-- - Monadic composition
-- - Lazy evaluation
--
-- Author: bad-antics
-- License: MIT
module Main where
import Data.List (sortBy, groupBy, nub, intercalate)
import Data.Ord (comparing)
import Data.Function (on)
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Char (toLower, isHexDigit)
import Text.Printf (printf)
import System.Environment (getArgs)
-- Version
version :: String
version = "1.0.0"
-- ANSI Colors
red, green, yellow, cyan, gray, reset :: String
red = "\x1b[31m"
green = "\x1b[32m"
yellow = "\x1b[33m"
cyan = "\x1b[36m"
gray = "\x1b[90m"
reset = "\x1b[0m"
-- Severity levels
data Severity = Critical | High | Medium | Low | Info
deriving (Show, Eq, Ord)
severityColor :: Severity -> String
severityColor Critical = red
severityColor High = red
severityColor Medium = yellow
severityColor Low = cyan
severityColor Info = gray
-- Protocol types
data Protocol = TCP | UDP | ICMP | DNS | HTTP | HTTPS | SSH | Unknown
deriving (Show, Eq, Ord)
-- Flow direction
data Direction = Inbound | Outbound | Bidirectional
deriving (Show, Eq)
-- IP Address (simplified)
data IPAddress = IPv4 Int Int Int Int | IPv6 String
deriving (Eq)
instance Show IPAddress where
show (IPv4 a b c d) = printf "%d.%d.%d.%d" a b c d
show (IPv6 s) = s
-- Network flow record
data Flow = Flow
{ flowSrcIP :: IPAddress
, flowDstIP :: IPAddress
, flowSrcPort :: Int
, flowDstPort :: Int
, flowProtocol :: Protocol
, flowBytes :: Int
, flowPackets :: Int
, flowDuration :: Float
, flowFlags :: [String]
} deriving (Show)
-- Flow analysis result
data FlowAnalysis = FlowAnalysis
{ analysisFlow :: Flow
, analysisSeverity :: Severity
, analysisCategory :: String
, analysisDetails :: String
, analysisMitre :: Maybe String
} deriving (Show)
-- Suspicious port list
suspiciousPorts :: [(Int, String, Severity)]
suspiciousPorts =
[ (4444, "Metasploit default", High)
, (1337, "Elite/Backdoor", High)
, (31337, "Back Orifice", Critical)
, (6667, "IRC (C2)", Medium)
, (6666, "IRC alternate", Medium)
, (5555, "Android ADB", High)
, (9001, "Tor default", Medium)
, (9050, "Tor SOCKS", Medium)
, (3389, "RDP", Medium)
, (5900, "VNC", Medium)
, (2222, "Alt SSH", Low)
, (8080, "HTTP Proxy", Low)
, (8443, "HTTPS Alt", Low)
, (445, "SMB", Medium)
, (139, "NetBIOS", Medium)
, (23, "Telnet", High)
, (21, "FTP", Medium)
]
-- Known malicious IPs (demo data)
maliciousIPs :: [IPAddress]
maliciousIPs =
[ IPv4 185 220 101 1 -- Tor exit
, IPv4 23 129 64 100 -- Known C2
, IPv4 91 219 236 100 -- Bulletproof hosting
, IPv4 45 33 32 156 -- scanme.nmap.org (for demo)
]
-- Check if IP is in malicious list
isMalicious :: IPAddress -> Bool
isMalicious ip = ip `elem` maliciousIPs
-- Check if IP is private
isPrivate :: IPAddress -> Bool
isPrivate (IPv4 10 _ _ _) = True
isPrivate (IPv4 172 b _ _) = b >= 16 && b <= 31
isPrivate (IPv4 192 168 _ _) = True
isPrivate (IPv4 127 _ _ _) = True
isPrivate _ = False
-- Get protocol from port
protocolFromPort :: Int -> Protocol
protocolFromPort 22 = SSH
protocolFromPort 80 = HTTP
protocolFromPort 443 = HTTPS
protocolFromPort 53 = DNS
protocolFromPort _ = Unknown
-- Analyze a single flow
analyzeFlow :: Flow -> [FlowAnalysis]
analyzeFlow flow = portAnalysis ++ ipAnalysis ++ volumeAnalysis ++ flagAnalysis
where
-- Port-based analysis
portAnalysis = mapMaybe checkPort suspiciousPorts
checkPort (port, desc, sev)
| flowDstPort flow == port = Just FlowAnalysis
{ analysisFlow = flow
, analysisSeverity = sev
, analysisCategory = "Suspicious Port"
, analysisDetails = printf "Connection to port %d (%s)" port desc
, analysisMitre = Just "T1571"
}
| flowSrcPort flow == port = Just FlowAnalysis
{ analysisFlow = flow
, analysisSeverity = sev
, analysisCategory = "Suspicious Port"
, analysisDetails = printf "Connection from port %d (%s)" port desc
, analysisMitre = Just "T1571"
}
| otherwise = Nothing
-- IP-based analysis
ipAnalysis
| isMalicious (flowDstIP flow) = [FlowAnalysis
{ analysisFlow = flow
, analysisSeverity = Critical
, analysisCategory = "Malicious IP"
, analysisDetails = printf "Connection to known malicious IP: %s" (show $ flowDstIP flow)
, analysisMitre = Just "T1071"
}]
| isMalicious (flowSrcIP flow) = [FlowAnalysis
{ analysisFlow = flow
, analysisSeverity = Critical
, analysisCategory = "Malicious IP"
, analysisDetails = printf "Connection from known malicious IP: %s" (show $ flowSrcIP flow)
, analysisMitre = Just "T1071"
}]
| otherwise = []
-- Volume analysis
volumeAnalysis
| flowBytes flow > 100000000 = [FlowAnalysis -- > 100MB
{ analysisFlow = flow
, analysisSeverity = High
, analysisCategory = "Data Exfiltration"
, analysisDetails = printf "Large data transfer: %d bytes" (flowBytes flow)
, analysisMitre = Just "T1048"
}]
| flowBytes flow > 10000000 = [FlowAnalysis -- > 10MB
{ analysisFlow = flow
, analysisSeverity = Medium
, analysisCategory = "Large Transfer"
, analysisDetails = printf "Significant data transfer: %d bytes" (flowBytes flow)
, analysisMitre = Nothing
}]
| otherwise = []
-- Flag analysis
flagAnalysis
| "SYN" `elem` flowFlags flow && flowPackets flow > 1000 = [FlowAnalysis
{ analysisFlow = flow
, analysisSeverity = High
, analysisCategory = "Port Scan"
, analysisDetails = printf "SYN scan detected: %d packets" (flowPackets flow)
, analysisMitre = Just "T1046"
}]
| "RST" `elem` flowFlags flow && flowPackets flow > 500 = [FlowAnalysis
{ analysisFlow = flow
, analysisSeverity = Medium
, analysisCategory = "Connection Refused"
, analysisDetails = "High number of RST packets (possible probe)"
, analysisMitre = Just "T1046"
}]
| otherwise = []
-- Calculate flow rate
flowRate :: Flow -> Float
flowRate flow
| flowDuration flow > 0 = fromIntegral (flowBytes flow) / flowDuration flow
| otherwise = 0
-- Summarize flows
data FlowSummary = FlowSummary
{ summaryTotalFlows :: Int
, summaryTotalBytes :: Int
, summaryTotalPackets :: Int
, summaryProtocols :: [(Protocol, Int)]
, summaryTopTalkers :: [(IPAddress, Int)]
, summaryAlerts :: Int
} deriving (Show)
summarizeFlows :: [Flow] -> [FlowAnalysis] -> FlowSummary
summarizeFlows flows analyses = FlowSummary
{ summaryTotalFlows = length flows
, summaryTotalBytes = sum (map flowBytes flows)
, summaryTotalPackets = sum (map flowPackets flows)
, summaryProtocols = countProtocols flows
, summaryTopTalkers = topTalkers flows
, summaryAlerts = length analyses
}
countProtocols :: [Flow] -> [(Protocol, Int)]
countProtocols flows = map (\g -> (head g, length g)) grouped
where
protocols = map flowProtocol flows
grouped = groupBy (==) $ sortBy compare protocols
topTalkers :: [Flow] -> [(IPAddress, Int)]
topTalkers flows = take 5 $ sortBy (flip $ comparing snd) $
map (\g -> (head g, length g)) grouped
where
ips = map flowSrcIP flows ++ map flowDstIP flows
grouped = groupBy (==) $ sortBy compare (map show ips >>= \_ -> ips)
-- Demo flows
demoFlows :: [Flow]
demoFlows =
[ Flow (IPv4 192 168 1 100) (IPv4 185 220 101 1) 45678 443 HTTPS 50000 100 10.5 []
, Flow (IPv4 192 168 1 101) (IPv4 8 8 8 8) 54321 53 DNS 1500 20 0.5 []
, Flow (IPv4 10 0 0 50) (IPv4 45 33 32 156) 12345 4444 TCP 250000 500 30.0 []
, Flow (IPv4 172 16 0 100) (IPv4 91 219 236 100) 33333 6667 TCP 15000 50 120.0 []
, Flow (IPv4 192 168 1 1) (IPv4 192 168 1 255) 0 0 ICMP 64 1 0.001 []
, Flow (IPv4 10 10 10 10) (IPv4 203 0 113 50) 22 22 SSH 500000 1000 300.0 ["SYN", "ACK"]
, Flow (IPv4 192 168 100 5) (IPv4 1 2 3 4) 50000 31337 TCP 150000000 50000 600.0 []
, Flow (IPv4 192 168 1 50) (IPv4 104 244 42 129) 40000 443 HTTPS 25000 80 5.0 []
, Flow (IPv4 10 0 0 1) (IPv4 10 0 0 254) 0 0 TCP 0 5000 1.0 ["SYN"]
, Flow (IPv4 192 168 5 5) (IPv4 192 168 5 1) 3389 54321 TCP 1000000 2000 60.0 []
]
-- Printing functions
printBanner :: IO ()
printBanner = do
putStrLn ""
putStrLn "╔══════════════════════════════════════════════════════════════════╗"
putStrLn "║ NullSec FlowTrace - Network Flow Analyzer ║"
putStrLn "╚══════════════════════════════════════════════════════════════════╝"
putStrLn ""
printUsage :: IO ()
printUsage = do
putStrLn "USAGE:"
putStrLn " flowtrace [OPTIONS] <file>"
putStrLn ""
putStrLn "OPTIONS:"
putStrLn " -h, --help Show this help"
putStrLn " -j, --json JSON output"
putStrLn " -s, --summary Summary only"
putStrLn " -v, --verbose Verbose output"
putStrLn ""
putStrLn "SUPPORTED FORMATS:"
putStrLn " • NetFlow v5/v9"
putStrLn " • IPFIX"
putStrLn " • Zeek conn.log"
putStrLn " • Argus flows"
printAnalysis :: FlowAnalysis -> IO ()
printAnalysis analysis = do
let sev = analysisSeverity analysis
col = severityColor sev
flow = analysisFlow analysis
putStrLn ""
putStrLn $ printf " %s[%s]%s %s" col (show sev) reset (analysisCategory analysis)
putStrLn $ printf " Source: %s:%d" (show $ flowSrcIP flow) (flowSrcPort flow)
putStrLn $ printf " Dest: %s:%d" (show $ flowDstIP flow) (flowDstPort flow)
putStrLn $ printf " Detail: %s" (analysisDetails analysis)
case analysisMitre analysis of
Just m -> putStrLn $ printf " MITRE: %s" m
Nothing -> return ()
printSummary :: FlowSummary -> IO ()
printSummary summary = do
putStrLn ""
putStrLn $ gray ++ "═══════════════════════════════════════════" ++ reset
putStrLn ""
putStrLn " Summary:"
putStrLn $ printf " Total Flows: %d" (summaryTotalFlows summary)
putStrLn $ printf " Total Bytes: %d" (summaryTotalBytes summary)
putStrLn $ printf " Total Packets: %d" (summaryTotalPackets summary)
putStrLn $ printf " Alerts: %s%d%s"
(if summaryAlerts summary > 0 then red else green)
(summaryAlerts summary)
reset
putStrLn ""
putStrLn " Top Protocols:"
mapM_ (\(p, c) -> putStrLn $ printf " • %s: %d flows" (show p) c)
(summaryProtocols summary)
demoMode :: IO ()
demoMode = do
putStrLn $ yellow ++ "[Demo Mode]" ++ reset
putStrLn ""
putStrLn $ cyan ++ "Analyzing sample network flows..." ++ reset
let analyses = concatMap analyzeFlow demoFlows
sortedAnalyses = sortBy (comparing analysisSeverity) analyses
summary = summarizeFlows demoFlows analyses
putStrLn $ printf "\nProcessed %d flows, found %d alerts\n"
(length demoFlows) (length analyses)
mapM_ printAnalysis sortedAnalyses
printSummary summary
main :: IO ()
main = do
printBanner
args <- getArgs
case args of
[] -> do
printUsage
putStrLn ""
demoMode
("-h":_) -> printUsage
("--help":_) -> printUsage
_ -> do
printUsage
putStrLn ""
demoMode