diff options
Diffstat (limited to 'readpackets.hs')
-rw-r--r-- | readpackets.hs | 286 |
1 files changed, 249 insertions, 37 deletions
diff --git a/readpackets.hs b/readpackets.hs index f02df538..690aa91e 100644 --- a/readpackets.hs +++ b/readpackets.hs | |||
@@ -1,6 +1,9 @@ | |||
1 | {-# LANGUAGE PackageImports #-} | 1 | {-# LANGUAGE PackageImports #-} |
2 | {-# LANGUAGE StandaloneDeriving #-} | ||
2 | module Main where | 3 | module Main where |
3 | 4 | ||
5 | import Control.Arrow | ||
6 | import Data.Maybe | ||
4 | import Data.Binary.Get (runGet) | 7 | import Data.Binary.Get (runGet) |
5 | import qualified Data.ByteString as BS | 8 | import qualified Data.ByteString as BS |
6 | import qualified Data.ByteString as B | 9 | import qualified Data.ByteString as B |
@@ -8,30 +11,63 @@ import qualified Data.ByteString.Lazy as LZ | |||
8 | import qualified Data.ByteString.Lazy.Char8 as L8 | 11 | import qualified Data.ByteString.Lazy.Char8 as L8 |
9 | import Data.IORef | 12 | import Data.IORef |
10 | import Data.List | 13 | import Data.List |
14 | import Data.Word | ||
11 | import Debug.Trace | 15 | import Debug.Trace |
12 | import Text.Printf | 16 | import Text.Printf |
13 | import Text.Show.Pretty as PP | 17 | import Text.Show.Pretty as PP |
14 | import "network-house" Net.Packet | 18 | import "network-house" Net.Packet as House |
15 | import qualified "network-house" Net.IPv4 as IP4 | 19 | import qualified "network-house" Net.IPv4 as IP4 |
16 | import qualified "network-house" Net.IPv6 as IP6 | 20 | import qualified "network-house" Net.IPv6 as IP6 |
17 | import "network-house" Net.PacketParsing | 21 | import "network-house" Net.PacketParsing as House |
22 | import "network-house" Net.PortNumber as TCP | ||
23 | import "network-house" Net.ICMP as ICMP | ||
24 | import "network-house" Net.TCP as TCP | ||
18 | import "network-house" Net.UDP as UDP | 25 | import "network-house" Net.UDP as UDP |
19 | import "pcap" Network.Pcap | 26 | import "pcap" Network.Pcap |
20 | import qualified Data.Serialize as S | 27 | import qualified Data.Serialize as S |
21 | import qualified Network.Socket as HS | 28 | import qualified Network.Socket as HS |
22 | import Control.Applicative | 29 | import Control.Applicative |
30 | import Control.Monad | ||
31 | import System.Environment | ||
32 | import Control.Exception | ||
33 | |||
34 | import Hans.Checksum | ||
35 | import WifiHeader as Wifi | ||
23 | 36 | ||
24 | import Crypto.Tox | 37 | import Crypto.Tox |
25 | import Network.Tox.DHT.Transport as Tox | 38 | import Network.Tox.DHT.Transport as Tox |
26 | import Data.BEncode as BE | 39 | import Data.BEncode as BE |
27 | import Data.BEncode.Pretty | 40 | import Data.BEncode.Pretty |
28 | -- import Data.IKE.Message | 41 | -- import Data.IKE.Message |
42 | import Text.XXD | ||
29 | 43 | ||
30 | -- traceM string = trace string $ return () | 44 | -- traceM string = trace string $ return () |
31 | 45 | ||
46 | |||
47 | -- IPv4 : rfc 791 | ||
48 | -- IPv6 : rfc 2460 (8200) | ||
49 | -- TCP : rfc 793 | ||
50 | -- UDP : rfc 768 | ||
51 | -- ICMP : rfc 792 | ||
52 | -- ICMPv6 : rfc 4443 | ||
53 | -- ARP : rfc 826 | ||
54 | -- SNAP : rfc 1042 | ||
55 | |||
56 | deriving instance Show IP6.Word4 | ||
57 | deriving instance Show IP6.Word20 | ||
58 | deriving instance Show IP6.Addr | ||
59 | deriving instance Show a => Show (IP6.Packet a) | ||
60 | |||
32 | bs2chunk :: BS.ByteString -> UArray Int Word8 | 61 | bs2chunk :: BS.ByteString -> UArray Int Word8 |
33 | bs2chunk bs = listArray (0,subtract 33 $ BS.length bs) $ drop 32 $ BS.unpack bs | 62 | bs2chunk bs = listArray (0,subtract 33 $ BS.length bs) $ drop 32 $ BS.unpack bs |
63 | -- 23 + 9 = 32 | ||
64 | |||
65 | mkchunk :: Int -> BS.ByteString -> Maybe (UArray Int Word8) | ||
66 | mkchunk n bs | ||
67 | | (BS.length bs < n) = Nothing | ||
68 | | otherwise = Just $ listArray (0,subtract (n+1) $ BS.length bs) $ drop n $ BS.unpack bs | ||
34 | 69 | ||
70 | {- | ||
35 | hex :: BS.ByteString -> String | 71 | hex :: BS.ByteString -> String |
36 | hex = concatMap (printf "%02x") . B.unpack | 72 | hex = concatMap (printf "%02x") . B.unpack |
37 | 73 | ||
@@ -40,43 +76,219 @@ hexlines bs = ss | |||
40 | where xs = zip [0..] $ hex bs | 76 | where xs = zip [0..] $ hex bs |
41 | ls = groupBy (\(n,_) (m,_)-> n `div` 32 == m `div` 32) xs | 77 | ls = groupBy (\(n,_) (m,_)-> n `div` 32 == m `div` 32) xs |
42 | ss = map (map snd) ls | 78 | ss = map (map snd) ls |
79 | -} | ||
80 | |||
81 | udpPacket :: PktHdr -> BS.ByteString -> Maybe (IO ()) | ||
82 | udpPacket hdr buf = do | ||
83 | chunk <- mkchunk 32 buf | ||
84 | udp <- House.doParse $ House.toInPack chunk | ||
85 | -- traceM $ "got udp: " ++ show udp | ||
86 | -- traceM $ "got udp content: " ++ show (content udp) | ||
87 | let plen = House.len $ UDP.content udp | ||
88 | bs <- fmap BS.pack . parseInPacket (bytes plen) $ UDP.content udp | ||
89 | -- traceM $ "Got bs " ++ show bs | ||
90 | (checksum,blob) <- Just $ BS.splitAt 2 bs -- extra 2 bytes in pcap capture, i'm assuming its a checksum | ||
91 | -- (first4,truncated) = BS.splitAt 4 blob | ||
92 | -- -- First 4 bytes being zero is how we distinguish between | ||
93 | -- -- ESP and IKEv2 packets on port 4500. | ||
94 | -- dta = if destPort udp /= Port 500 && first4==BS.pack [0,0,0,0] | ||
95 | -- then truncated | ||
96 | -- else blob | ||
97 | Just $ do | ||
98 | putStrLn $ "UDP." ++ show udp | ||
99 | mapM_ putStrLn $ xxd2 0 blob | ||
100 | |||
101 | parseIP4 :: InPacket -> Maybe (IP4.Packet InPacket) | ||
102 | parseIP4 inpkt = do | ||
103 | ip <- House.doParse inpkt | ||
104 | 4 <- Just $ IP4.version ip | ||
105 | return ip | ||
106 | |||
107 | ipPacket4 :: PktHdr -> BS.ByteString -> Maybe (IO ()) | ||
108 | ipPacket4 hdr buf = do | ||
109 | chunk <- mkchunk 23 buf | ||
110 | ip <- parseIP4 $ House.toInPack chunk | ||
111 | Just $ do | ||
112 | putStrLn $ "-- ipPacket4 --" | ||
113 | putStrLn $ "IP4." ++ show ip | ||
114 | |||
115 | parseIP6 :: InPacket -> Maybe (IP6.Packet InPacket) | ||
116 | parseIP6 inpkt = do | ||
117 | ip <- House.doParse inpkt | ||
118 | IP6.Word4 6 <- Just $ IP6.version ip | ||
119 | return ip | ||
120 | |||
121 | ipPacket6 :: PktHdr -> BS.ByteString -> Maybe (IO ()) | ||
122 | ipPacket6 hdr buf = do | ||
123 | chunk <- mkchunk 23 buf | ||
124 | ip <- parseIP6 $ House.toInPack chunk | ||
125 | Just $ do | ||
126 | putStrLn $ "-- ipPacket6 --" | ||
127 | putStrLn $ "IP6." ++ show ip | ||
128 | |||
129 | showTruncated hdr s | ||
130 | | hdrCaptureLength hdr == hdrWireLength hdr | ||
131 | = s | ||
132 | | otherwise = unwords | ||
133 | [ s | ||
134 | , "(truncated" | ||
135 | , show $ hdrCaptureLength hdr | ||
136 | , "of" | ||
137 | , show (hdrWireLength hdr) ++ ")" | ||
138 | ] | ||
139 | |||
140 | wifiPacket hdr buf = do | ||
141 | (Data,_) <- wifiTypeBits $ BS.index buf 0 -- Filter all but data packets. | ||
142 | guard (BS.length buf >= 28) | ||
143 | chunk <- mkchunk 0 buf | ||
144 | wifi <- House.doParse $ House.toInPack chunk | ||
145 | Just $ do | ||
146 | -- putStrLn $ "-- wifiPacket --" | ||
147 | -- putStrLn $ "Wifi." ++ show wifi | ||
148 | let parseip = do | ||
149 | Wifi.Data <- Just $ wifiType wifi | ||
150 | c <- Just $ Wifi.content wifi | ||
151 | (Left <$> parseIP4 c) <|> (Right <$> parseIP6 c) | ||
152 | let mdta = parseip >>= ipPacket | ||
153 | forM_ mdta $ \dta -> do | ||
154 | let src = saddr (left fst $ right fst $ ipAddrs dta) | ||
155 | (fromMaybe 0 $ fst <$> ipPorts dta) | ||
156 | dst = saddr (left snd $ right snd $ ipAddrs dta) | ||
157 | (fromMaybe 0 $ snd <$> ipPorts dta) | ||
158 | srcmac = Wifi.srcAddr (Wifi.addresses wifi) | ||
159 | dstmac = Wifi.dstAddr (Wifi.addresses wifi) | ||
160 | r = [ maybe "" (("bssid "++) . show) $ Wifi.bssid (Wifi.addresses wifi) | ||
161 | , "<-- " ++ show src ++ " (" ++ show srcmac ++ ")" | ||
162 | , "--> " ++ show dst ++ " (" ++ show dstmac ++ ")" | ||
163 | , showTruncated hdr (ipProtocol dta) | ||
164 | ] | ||
165 | case ipPayload dta of | ||
166 | Nothing -> mapM_ putStrLn r | ||
167 | Just bs -> mapM_ putStrLn $ sideBySide (xxd2 0 bs) r | ||
168 | putStrLn "" | ||
169 | |||
170 | sideBySide (x:xs) (y:ys) = (take 72 (x ++ repeat ' ') ++ y) : sideBySide xs ys | ||
171 | sideBySide [] (y:ys) = (replicate 72 ' ' ++ y) : sideBySide [] ys | ||
172 | sideBySide xs [] = xs | ||
173 | |||
174 | saddr (Left ip4) port = HS.SockAddrInet (fromIntegral port) addr | ||
175 | where IP4.Addr a b c d = ip4 | ||
176 | addr = fromIntegral a | ||
177 | + fromIntegral b * 256 | ||
178 | + fromIntegral c * 65536 | ||
179 | + fromIntegral d * 16777216 | ||
180 | saddr (Right ip6) port = HS.SockAddrInet6 (fromIntegral port) 0 addr 0 | ||
181 | where IP6.Addr ah al bh bl ch cl dh dl = ip6 | ||
182 | addr = ( fromIntegral ah * 65536 + fromIntegral al | ||
183 | , fromIntegral bh * 65536 + fromIntegral bl | ||
184 | , fromIntegral ch * 65536 + fromIntegral cl | ||
185 | , fromIntegral dh * 65536 + fromIntegral dl | ||
186 | ) | ||
187 | |||
188 | data IPData = IPData | ||
189 | { ipPayload :: Maybe BS.ByteString | ||
190 | , ipProtocol :: String | ||
191 | , ipPorts :: Maybe (Word16,Word16) | ||
192 | , ipAddrs :: Either (IP4.Addr,IP4.Addr) | ||
193 | (IP6.Addr,IP6.Addr) | ||
194 | } | ||
195 | |||
196 | ip4addrs pkt = (IP4.source pkt, IP4.dest pkt) | ||
197 | |||
198 | ip6addrs pkt = (IP6.source pkt, IP6.dest pkt) | ||
199 | |||
200 | ipPacket ip = do | ||
201 | let udp = do | ||
202 | IP4.UDP <- Just $ either IP4.protocol IP6.next_header ip | ||
203 | pkt <- House.doParse $ either IP4.content IP6.content ip | ||
204 | let ulen = House.len $ UDP.content pkt | ||
205 | mbs = fmap BS.pack . parseInPacket (bytes ulen) $ UDP.content pkt | ||
206 | let UDP.Port sport = UDP.sourcePort pkt | ||
207 | UDP.Port dport = UDP.destPort pkt | ||
208 | return IPData | ||
209 | { ipPayload = mbs | ||
210 | , ipProtocol = "UDP" | ||
211 | , ipPorts = Just (sport,dport) | ||
212 | , ipAddrs = either (Left . ip4addrs) (Right . ip6addrs) ip | ||
213 | } | ||
214 | tcp = do | ||
215 | IP4.TCP <- Just $ either IP4.protocol IP6.next_header ip | ||
216 | pkt <- House.doParse $ either IP4.content IP6.content ip | ||
217 | let ulen = House.len $ TCP.content pkt | ||
218 | mbs = fmap BS.pack . parseInPacket (bytes ulen) $ TCP.content pkt | ||
219 | let TCP.Port sport = TCP.sourcePort pkt | ||
220 | TCP.Port dport = TCP.destPort pkt | ||
221 | return IPData | ||
222 | { ipPayload = mbs | ||
223 | , ipProtocol = "TCP" | ||
224 | , ipPorts = Just (sport,dport) | ||
225 | , ipAddrs = either (Left . ip4addrs) (Right . ip6addrs) ip | ||
226 | } | ||
227 | icmp = do | ||
228 | IP4.ICMP <- Just $ either IP4.protocol IP6.next_header ip | ||
229 | pkt <- House.doParse $ either IP4.content IP6.content ip | ||
230 | let ping = do | ||
231 | EchoRequest msg <- Just pkt | ||
232 | return ("Ping", echoData msg) | ||
233 | pong = do | ||
234 | EchoReply msg <- Just pkt | ||
235 | return ("Pong", echoData msg) | ||
236 | other = do | ||
237 | Other { type_ = typ, content = chunk } <- Just pkt | ||
238 | return (show typ, chunk) | ||
239 | (typ,chunk) <- ping <|> pong <|> other | ||
240 | let p = House.toInPack chunk | ||
241 | ulen = House.len p | ||
242 | mbs = fmap BS.pack . parseInPacket (bytes ulen) $ p | ||
243 | return IPData | ||
244 | { ipPayload = mbs | ||
245 | , ipProtocol = "ICMP "++typ | ||
246 | , ipPorts = Nothing | ||
247 | , ipAddrs = either (Left . ip4addrs) (Right . ip6addrs) ip | ||
248 | } | ||
249 | unknown = do | ||
250 | IP4.Unknown proto <- Just $ either IP4.protocol IP6.next_header ip | ||
251 | pkt <- Just $ either IP4.content IP6.content ip | ||
252 | let ulen = House.len pkt | ||
253 | mbs = fmap BS.pack . parseInPacket (bytes ulen) $ pkt | ||
254 | return IPData | ||
255 | { ipPayload = mbs | ||
256 | , ipProtocol = "proto "++show proto | ||
257 | , ipPorts = Nothing | ||
258 | , ipAddrs = either (Left . ip4addrs) (Right . ip6addrs) ip | ||
259 | } | ||
260 | udp <|> icmp <|> tcp <|> unknown | ||
261 | |||
43 | 262 | ||
44 | parsePacket :: IORef Int -> PktHdr -> BS.ByteString -> IO () | 263 | parsePacket :: IORef Int -> PktHdr -> BS.ByteString -> IO () |
45 | parsePacket cnt hdr buf = do | 264 | parsePacket cnt hdr buf = do |
46 | print hdr | 265 | sequence_ $ wifiPacket hdr buf |
47 | let -- mb :: Maybe (BS.ByteString, Message Encrypted) | ||
48 | mb = do | ||
49 | udp <- doParse $ toInPack $ bs2chunk buf | ||
50 | -- traceM $ "got udp: " ++ show udp | ||
51 | -- traceM $ "got udp content: " ++ show (content udp) | ||
52 | let plen = Net.Packet.len $ content udp | ||
53 | bs <- fmap BS.pack . parseInPacket (bytes plen) $ content udp | ||
54 | -- traceM $ "Got bs " ++ show bs | ||
55 | let (checksum,blob) = BS.splitAt 2 bs -- extra 2 bytes in pcap capture, i'm assuming its a checksum | ||
56 | -- (first4,truncated) = BS.splitAt 4 blob | ||
57 | -- -- First 4 bytes being zero is how we distinguish between | ||
58 | -- -- ESP and IKEv2 packets on port 4500. | ||
59 | -- dta = if destPort udp /= Port 500 && first4==BS.pack [0,0,0,0] | ||
60 | -- then truncated | ||
61 | -- else blob | ||
62 | dta = blob | ||
63 | let d = BE.decode {- runGet getMessage $ LZ.fromStrict -} dta | ||
64 | saddr = HS.SockAddrInet 0 0 -- TODO | ||
65 | -- e = S.decode dta :: Either String (DHTMessage Encrypted8) | ||
66 | e = case Tox.parseDHTAddr (dta,saddr) :: Either (DHTMessage Encrypted8,NodeInfo) (BS.ByteString,HS.SockAddr) of | ||
67 | Left toxpkt -> Right toxpkt | ||
68 | Right _ -> Left "tox parse fail" | ||
69 | return $ (udp, bs, fmap Left d <|> fmap Right e) | ||
70 | flip (maybe $ return ()) mb $ \(udp, bs,m) -> do | ||
71 | putStrLn $ show udp | ||
72 | mapM_ putStrLn $ hexlines bs | ||
73 | -- putStrLn $ PP.ppShow m | ||
74 | either putStrLn (L8.putStrLn . either showBEncode (L8.pack . show)) m | ||
75 | modifyIORef' cnt (+1) | 266 | modifyIORef' cnt (+1) |
76 | 267 | ||
268 | sampleHdr :: PktHdr | ||
269 | sampleHdr = PktHdr {hdrSeconds = 1511875620, hdrUseconds = 464364, hdrCaptureLength = 347, hdrWireLength = 347} | ||
270 | |||
271 | sampleBytes :: BS.ByteString | ||
272 | sampleBytes = "\136\n0\NULT\242\SOH\243\ENQ\153\NUL\rg\144\162\a\244>\157\ETXV\213\240 \ETX\NUL\170\170\ETX\NUL\NUL\NUL\b\NULE\NUL\SOH9\191\170@\NUL@\ACK\174\176\192,D\ENQ\n\235\188G\NULP\202\220R\199)\154R\199+\247\128\DC1\b\NUL\204\196\NUL\NUL\SOH\SOH\b\n\NUL\243\150\&6\NUL\243\150\&1HTTP/1.1 302 Found \r\nLocation: https://wifilogin.xfinity.com/start.php?h=kozHorIQv1nb851zZcMfnWdD4wtjyIymVSIwanl8KXaQLMHBm0oJhcubjREusjojI%2B8f0UNFoG1p2lD33OXGlpw3uvJ5SeKeX37jANRRu%2FPZOwWw%2F70foAx70wBVohtmhWksbshKhR4lAWuv2FAB0%2BPVkpfSjNIE0HwQO%2BnRZR8%3D\r\n\r\n" | ||
273 | |||
274 | sampleHdr2 = PktHdr {hdrSeconds = 1511841845, hdrUseconds = 681965, hdrCaptureLength = 10, hdrWireLength = 10} | ||
275 | sampleBytes2 = | ||
276 | [ 0xc4, 0x00, 0x8a, 0x00 | ||
277 | , 0x8c, 0xf5, 0xa3, 0x12, 0xdf, 0x80 ] | ||
278 | |||
77 | main = do | 279 | main = do |
78 | cnt <- newIORef 0 | 280 | args <- getArgs |
79 | pcap <- openOffline "packets.pcap" | 281 | forM_ args $ \fname -> do |
80 | loopResult <- loopBS pcap (-1) $ parsePacket cnt | 282 | cnt <- newIORef 0 |
81 | pktcnt <- readIORef cnt | 283 | parsePacket cnt sampleHdr sampleBytes |
82 | putStrLn $ "read "++show pktcnt ++" packets." | 284 | pcap <- openOffline fname -- "packets.pcap" |
285 | linktype <- datalink pcap | ||
286 | putStrLn $ unwords ["Opened:",fname,"Link:",show linktype] | ||
287 | -- Opened: opnwep.dat-08.cap Link: DLT_IEEE802_11 | ||
288 | -- Opened: packets.pcap Link: DLT_EN10MB | ||
289 | -- DLT_IEEE802_11 | ||
290 | -- DLT_IEEE802_11_RADIO | ||
291 | -- DLT_IEEE802_11_RADIO_AVS | ||
292 | loopResult <- loopBS pcap (-1) $ parsePacket cnt | ||
293 | pktcnt <- readIORef cnt | ||
294 | putStrLn $ "read "++show pktcnt ++" packets from " ++ fname ++ "." | ||