Attacks.hs (6513B)
1 {-# LANGUAGE ApplicativeDo #-} 2 3 module Cryptopals.MAC.Attacks where 4 5 import qualified Control.Monad.Trans.Reader as R 6 import qualified Data.Binary.Get as BG 7 import qualified Data.Binary.Put as BP 8 import qualified Data.Bits as B 9 import qualified Data.ByteString as BS 10 import qualified Data.ByteString.Base16 as B16 11 import qualified Data.ByteString.Char8 as B8 12 import qualified Data.ByteString.Lazy as BSL 13 import qualified Data.ByteString.Lazy.Char8 as BL8 14 import qualified Data.IntMap.Strict as IMS 15 import qualified Data.List as L 16 import qualified Data.Text as T 17 import qualified Data.Time as TI 18 import qualified Cryptopals.MAC as CM 19 import qualified Cryptopals.Digest.Pure.MD4 as M 20 import qualified Cryptopals.Digest.Pure.SHA as S 21 import GHC.Word (Word8, Word32, Word64) 22 import qualified Network.HTTP as H 23 import Numeric (showHex) 24 import qualified System.Random.MWC as MWC 25 26 data SHA1Registers = SHA1Registers !Word32 !Word32 !Word32 !Word32 !Word32 27 deriving (Eq, Show) 28 29 data MD4Registers = MD4Registers !Word32 !Word32 !Word32 !Word32 30 deriving (Eq, Show) 31 32 sha1 :: SHA1Registers -> Word64 -> BSL.ByteString -> BSL.ByteString 33 sha1 (SHA1Registers a b c d e) n s = 34 S.bytestringDigest $ S.sha1' a b c d e n s 35 36 md4 :: MD4Registers -> Word64 -> BSL.ByteString -> BSL.ByteString 37 md4 (MD4Registers a b c d) n s = M.md4' a b c d n s 38 39 raw :: BSL.ByteString 40 raw = mconcat [ 41 "comment1=cooking%20MCs;userdata=foo;" 42 , "comment2=%20like%20a%20pound%20of%20bacon" 43 ] 44 45 mal :: BSL.ByteString 46 mal = ";admin=true" 47 48 key :: IO BSL.ByteString 49 key = do 50 gen <- MWC.createSystemRandom 51 idx <- MWC.uniformR (0, 235885) gen 52 dict <- BL8.readFile "/usr/share/dict/words" 53 let ls = BL8.lines dict 54 pure $ ls !! idx 55 56 -- pad a message using the specified message length 57 pad :: Word64 -> BSL.ByteString -> BSL.ByteString 58 pad n bs = bs <> padding n where 59 padding n = BP.runPut $ do 60 BP.putWord8 128 61 loop (pred (pbytes n)) 62 63 loop l 64 | l == 0 = BP.putWord64be (n * 8) 65 | otherwise = do 66 BP.putWord8 0 67 loop (pred l) 68 69 pbytes ((\k -> 64 - k `mod` 64) -> l) 70 | l == 0 = l + 56 71 | otherwise = l - 8 72 73 -- sha1-keyed MAC via length extension 74 75 injectSha1 :: BSL.ByteString -> SHA1Registers 76 injectSha1 = BG.runGet $ do 77 a <- BG.getWord32be 78 b <- BG.getWord32be 79 c <- BG.getWord32be 80 d <- BG.getWord32be 81 e <- BG.getWord32be 82 pure $ SHA1Registers a b c d e 83 84 extractSha1 :: SHA1Registers -> BSL.ByteString 85 extractSha1 (SHA1Registers a b c d e) = BP.runPut $ do 86 BP.putWord32be a 87 BP.putWord32be b 88 BP.putWord32be c 89 BP.putWord32be d 90 BP.putWord32be e 91 92 leasha1 93 :: BSL.ByteString 94 -> BSL.ByteString 95 -> BSL.ByteString 96 -> R.Reader BSL.ByteString (BSL.ByteString, BSL.ByteString) 97 leasha1 input mac addl = loop 0 where 98 loop j = do 99 let len = fromIntegral $ BSL.length input 100 evil = pad (len + j) input <> addl 101 rs = injectSha1 mac 102 p = fromIntegral (BSL.length evil) + j 103 forged = sha1 rs p addl 104 validates <- oracleValidates evil forged 105 if validates 106 then pure (evil, forged) 107 else loop (succ j) 108 109 oracleValidates msg mac = do 110 k <- R.ask 111 pure $ CM.verifysha1mac k mac msg 112 113 -- md4-keyed MAC via length extension 114 115 -- little-endian 'pad' 116 padle :: Word64 -> BSL.ByteString -> BSL.ByteString 117 padle n bs = bs <> padding n where 118 padding n = BP.runPut $ do 119 BP.putWord8 128 120 loop (pred (pbytes n)) 121 122 loop l 123 | l == 0 = BP.putWord64le (n * 8) 124 | otherwise = do 125 BP.putWord8 0 126 loop (pred l) 127 128 pbytes ((\k -> 64 - k `mod` 64) -> l) 129 | l == 0 = l + 56 130 | otherwise = l - 8 131 132 injectMd4 :: BSL.ByteString -> MD4Registers 133 injectMd4 = BG.runGet $ do 134 a <- BG.getWord32le 135 b <- BG.getWord32le 136 c <- BG.getWord32le 137 d <- BG.getWord32le 138 pure $ MD4Registers a b c d 139 140 extractMd4 :: MD4Registers -> BSL.ByteString 141 extractMd4 (MD4Registers a b c d) = BP.runPut $ do 142 BP.putWord32le a 143 BP.putWord32le b 144 BP.putWord32le c 145 BP.putWord32le d 146 147 leamd4 148 :: BSL.ByteString 149 -> BSL.ByteString 150 -> BSL.ByteString 151 -> R.Reader BSL.ByteString (BSL.ByteString, BSL.ByteString) 152 leamd4 input mac addl = loop 0 where 153 loop j = do 154 let len = fromIntegral $ BSL.length input 155 evil = padle (len + j) input <> addl 156 rs = injectMd4 mac 157 p = fromIntegral (BSL.length evil) + j 158 forged = md4 rs p addl 159 validates <- oracleValidates evil forged 160 if validates 161 then pure (evil, forged) 162 else loop (succ j) 163 164 oracleValidates msg mac = do 165 k <- R.ask 166 pure $ CM.verifymd4mac k mac msg 167 168 -- timing attack on HMAC-SHA1 169 170 hmacValidates :: BS.ByteString -> BS.ByteString -> IO Bool 171 hmacValidates fil sig = do 172 let f = B8.unpack fil 173 s = T.unpack . B16.encodeBase16 $ sig 174 res <- H.simpleHTTP . H.getRequest $ 175 "http://localhost:3000/hmac?safe=false&delay=5&file=" <> f <> "&" <> 176 "signature=" <> s 177 cod <- H.getResponseCode res 178 pure $ cod == (2, 0, 0) 179 180 collect 181 :: BS.ByteString -- message 182 -> Int -- number of samples 183 -> BS.ByteString -- got so far 184 -> BS.ByteString -- remaining 185 -> IO (IMS.IntMap [TI.NominalDiffTime]) 186 collect !fil sam pre etc = loop mempty 0 0 where 187 loop !acc cyc b 188 | cyc == sam = pure acc 189 | otherwise = do 190 let !can = pre <> BS.cons b etc 191 org <- TI.getCurrentTime 192 cod <- hmacValidates fil can 193 end <- TI.getCurrentTime 194 let dif = TI.diffUTCTime end org 195 nac = IMS.alter (add dif) (fromIntegral b) acc 196 sik | b == 255 = succ cyc 197 | otherwise = cyc 198 loop nac sik (b + 1) 199 200 add d ma = case ma of 201 Nothing -> Just (d : []) 202 Just a -> Just (d : a) 203 204 crackByte 205 :: BS.ByteString 206 -> BS.ByteString 207 -> BS.ByteString 208 -> IO Word8 209 crackByte fil pre etc = do 210 samples <- collect fil 7 pre etc 211 let ver = fmap med samples 212 chu = IMS.foldlWithKey' 213 (\acc k v -> if v > snd acc then (k, v) else acc) 214 (256, 0) 215 ver 216 pure $ fromIntegral (fst chu) 217 218 crackHmac :: BS.ByteString -> IO BS.ByteString 219 crackHmac fil = loop mempty (BS.replicate 20 0) where 220 loop !acc sig = case BS.uncons sig of 221 Nothing -> pure acc 222 Just (_, t) -> do 223 byt <- crackByte fil acc t 224 let nex = BS.snoc acc byt 225 putStrLn $ "current guess: " <> show (B16.encodeBase16 nex) 226 loop nex t 227 228 avg :: (Foldable f, Fractional a) => f a -> a 229 avg l = sum l / fromIntegral (length l) 230 231 -- -- hacky median for container with known length 7 232 med :: Ord a => [a] -> a 233 med l = L.sort l !! 3