Attacks.hs (13578B)
1 module Cryptopals.Block.Attacks ( 2 chaosEncrypter 3 , alienEncrypter 4 , weirdEncrypter 5 ) where 6 7 import Control.Monad 8 import Control.Monad.Primitive 9 import qualified Control.Monad.ST as ST 10 import qualified Cryptopals.AES as AES 11 import qualified Cryptopals.Util as CU 12 import qualified Data.Bits as B 13 import qualified Data.ByteString as BS 14 import qualified Data.ByteString.Base16 as B16 15 import qualified Data.ByteString.Base64 as B64 16 import qualified Data.HashMap.Strict as HMS 17 import qualified Data.List as L 18 import qualified Data.Maybe as M 19 import qualified Data.Set as S 20 import qualified Data.Text as T 21 import qualified Data.Text.Encoding as TE 22 import GHC.Word (Word8) 23 import qualified System.Random.MWC as MWC 24 25 -- | An unknown AES key. 26 consistentKey :: BS.ByteString 27 consistentKey = ST.runST $ do 28 gen <- MWC.create 29 CU.bytes 16 gen 30 31 chaosEncrypter 32 :: PrimMonad m 33 => BS.ByteString 34 -> MWC.Gen (PrimState m) 35 -> m BS.ByteString 36 chaosEncrypter plaintext gen = do 37 key <- CU.bytes 16 gen 38 pre <- MWC.uniformR (5, 10) gen >>= flip CU.bytes gen 39 pos <- MWC.uniformR (5, 10) gen >>= flip CU.bytes gen 40 41 let tex = pre <> plaintext <> pos 42 bs = CU.lpkcs7 tex 43 44 ecb <- MWC.uniform gen 45 46 if ecb 47 then pure $ AES.encryptEcbAES128 key bs 48 else do 49 iv <- CU.bytes 16 gen 50 pure $ AES.encryptCbcAES128 iv key bs 51 52 alienEncrypter :: BS.ByteString -> BS.ByteString 53 alienEncrypter plaintext = 54 let pos = B64.decodeBase64Lenient $ mconcat [ 55 "Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkg" 56 , "aGFpciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBq" 57 , "dXN0IHRvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUg" 58 , "YnkK" 59 ] 60 61 par = plaintext <> pos 62 bs = CU.lpkcs7 par 63 64 in AES.encryptEcbAES128 consistentKey bs 65 66 ciphertextMap 67 :: (BS.ByteString -> BS.ByteString) 68 -> BS.ByteString 69 -> HMS.HashMap BS.ByteString Word8 70 ciphertextMap oracle input = loop [0..255] mempty where 71 loop ps !acc = case ps of 72 [] -> acc 73 (h:t) -> 74 let key = BS.take (CU.roundUpToMul 16 (BS.length input)) $ 75 oracle (input <> BS.singleton h) 76 in loop t (HMS.insert key h acc) 77 78 mciphertextMap 79 :: PrimMonad m 80 => (BS.ByteString -> MWC.Gen (PrimState m) -> m BS.ByteString) 81 -> BS.ByteString 82 -> MWC.Gen (PrimState m) 83 -> m (HMS.HashMap BS.ByteString Word8) 84 mciphertextMap oracle input = loop [0..255] mempty where 85 loop ps !acc gen = case ps of 86 [] -> pure acc 87 (h:t) -> do 88 ciph <- oracle (input <> BS.singleton h) gen 89 let key = BS.take (CU.roundUpToMul 16 (BS.length input)) $ ciph 90 loop t (HMS.insert key h acc) gen 91 92 incrByteEcbAttack :: (BS.ByteString -> BS.ByteString) -> BS.ByteString 93 incrByteEcbAttack oracle = loop input mempty where 94 ciphertext = oracle mempty 95 input = BS.replicate (BS.length ciphertext - 1) 65 96 97 loop !inp !plain = case BS.unsnoc inp of 98 Nothing -> plain 99 Just (bs, _) -> 100 let raw = oracle inp 101 quer = inp <> plain 102 dict = ciphertextMap oracle quer 103 key = BS.take (CU.roundUpToMul 16 (BS.length input)) raw 104 in case HMS.lookup key dict of 105 Nothing -> plain -- XX need better stopping condition? 106 Just byt -> loop bs (plain <> BS.singleton byt) 107 108 -- XX something probably a little off here; sometimes returns truncated 109 -- plaintexts 110 hardIncrByteEcbAttack 111 :: PrimMonad m 112 => (BS.ByteString -> MWC.Gen (PrimState m) -> m BS.ByteString) 113 -> MWC.Gen (PrimState m) 114 -> m BS.ByteString 115 hardIncrByteEcbAttack oracle gen = do 116 ciphertext <- oracle mempty gen 117 let input = BS.replicate (BS.length ciphertext - 1) 66 118 loop input mempty gen 119 where 120 loop !inp !plain g = case BS.unsnoc inp of 121 Nothing -> pure plain 122 Just (bs, _) -> do 123 raw <- oracle inp g 124 let quer = inp <> plain 125 dict <- mciphertextMap oracle quer g 126 let key = BS.take (CU.roundUpToMul 16 (BS.length (inp <> plain))) raw 127 case HMS.lookup key dict of 128 Nothing -> pure plain -- XX ? 129 Just byt -> loop bs (plain <> BS.singleton byt) g 130 131 kvParser :: T.Text -> HMS.HashMap T.Text T.Text 132 kvParser = L.foldl' alg mempty . T.splitOn "&" where 133 alg acc val = case T.splitOn "=" val of 134 (h:t:[]) -> HMS.insert h t acc 135 _ -> acc 136 137 profileFor :: T.Text -> T.Text 138 profileFor addr = 139 let email = T.filter (`notElem` ("&=" :: String)) addr 140 in "email=" <> email <> "&" <> "uid=10&role=user" 141 142 -- cut-and-paste ECB 143 cpeEncrypt :: BS.ByteString -> BS.ByteString 144 cpeEncrypt user = 145 let tex = TE.encodeUtf8 $ profileFor (TE.decodeUtf8 user) 146 147 bs = CU.lpkcs7 tex 148 149 in AES.encryptEcbAES128 consistentKey bs 150 151 -- cut-and-paste ECB 152 cpeDecrypt :: BS.ByteString -> BS.ByteString 153 cpeDecrypt ciphertext = AES.decryptEcbAES128 consistentKey ciphertext 154 155 weirdEncrypter 156 :: PrimMonad m 157 => BS.ByteString 158 -> MWC.Gen (PrimState m) 159 -> m BS.ByteString 160 weirdEncrypter plaintext gen = do 161 let pos = B64.decodeBase64Lenient $ mconcat [ 162 "Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkg" 163 , "aGFpciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBq" 164 , "dXN0IHRvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUg" 165 , "YnkK" 166 ] 167 168 bys <- MWC.uniformR (1, 256) gen 169 pre <- CU.bytes bys gen 170 171 let par = pre <> plaintext <> pos 172 bs = CU.lpkcs7 par 173 174 pure $ AES.encryptEcbAES128 consistentKey bs 175 176 -- The idea is to inject a block whose ciphertext is known, followed by 177 -- the malicious alignment block(s). One can figure out ciphertext 178 -- corresponding to any block of repeated bytes by just feeding in more 179 -- than a block's worth of them -- necessarily some (plaintext) block 180 -- will then include only that repeated byte. 181 -- 182 -- E.g.: "AAAAAAAAAAAAAAAA" encrypts to "57eef2e16c3867b9889350eb5732c183", 183 -- so we can look for that ciphertext in the result in order to locate 184 -- an "origin," only analyzing ciphertexts in which it appears. 185 -- 186 -- This function returns the ciphertext following the "identifier" block. 187 attackProxy 188 :: PrimMonad m 189 => (BS.ByteString -> MWC.Gen (PrimState m) -> m BS.ByteString) 190 -> BS.ByteString 191 -> MWC.Gen (PrimState m) 192 -> m BS.ByteString 193 attackProxy oracle input = loop where 194 identifier = BS.replicate 16 65 195 Right knownBlock = B16.decodeBase16 "57eef2e16c3867b9889350eb5732c183" 196 197 loop g = do 198 ciph <- oracle (identifier <> input) g 199 let (_, target) = BS.breakSubstring knownBlock ciph 200 if target == mempty 201 then loop g 202 else pure $ BS.drop 16 target 203 204 -- bitflipping CBC 205 206 bfcEncrypter :: BS.ByteString -> BS.ByteString 207 bfcEncrypter input = AES.encryptCbcAES128 iv consistentKey padded where 208 iv = BS.replicate 16 0 209 filtered = BS.filter (`notElem` (BS.unpack ";=")) input 210 plaintext = "comment1=cooking%20MCs;userdata=" <> filtered <> 211 ";comment2=%20like%20a%20pound%20of%20bacon" 212 padded = CU.lpkcs7 plaintext 213 214 bfcChecker :: BS.ByteString -> Bool 215 bfcChecker ciphertext = target /= mempty where 216 iv = BS.replicate 16 0 217 plaintext = AES.decryptCbcAES128 consistentKey ciphertext 218 (_, target) = BS.breakSubstring ";admin=true;" plaintext 219 220 -- CBC padding oracle 221 222 -- see https://en.wikipedia.org/wiki/Padding_oracle_attack 223 poInputs :: [BS.ByteString] 224 poInputs = [ 225 "MDAwMDAwTm93IHRoYXQgdGhlIHBhcnR5IGlzIGp1bXBpbmc=" 226 , "MDAwMDAxV2l0aCB0aGUgYmFzcyBraWNrZWQgaW4gYW5kIHRoZSBWZWdhJ3MgYXJlIHB1bXBpbic=" 227 , "MDAwMDAyUXVpY2sgdG8gdGhlIHBvaW50LCB0byB0aGUgcG9pbnQsIG5vIGZha2luZw==" 228 , "MDAwMDAzQ29va2luZyBNQydzIGxpa2UgYSBwb3VuZCBvZiBiYWNvbg==" 229 , "MDAwMDA0QnVybmluZyAnZW0sIGlmIHlvdSBhaW4ndCBxdWljayBhbmQgbmltYmxl" 230 , "MDAwMDA1SSBnbyBjcmF6eSB3aGVuIEkgaGVhciBhIGN5bWJhbA==" 231 , "MDAwMDA2QW5kIGEgaGlnaCBoYXQgd2l0aCBhIHNvdXBlZCB1cCB0ZW1wbw==" 232 , "MDAwMDA3SSdtIG9uIGEgcm9sbCwgaXQncyB0aW1lIHRvIGdvIHNvbG8=" 233 , "MDAwMDA4b2xsaW4nIGluIG15IGZpdmUgcG9pbnQgb2g=" 234 , "MDAwMDA5aXRoIG15IHJhZy10b3AgZG93biBzbyBteSBoYWlyIGNhbiBibG93" 235 ] 236 237 paddingOracle 238 :: PrimMonad m 239 => MWC.Gen (PrimState m) 240 -> m BS.ByteString 241 paddingOracle gen = do 242 idx <- MWC.uniformR (0, length poInputs - 1) gen 243 let Right input = B64.decodeBase64 (poInputs !! idx) 244 padded = CU.lpkcs7 input 245 iv <- CU.bytes 16 gen 246 pure $ AES.encryptCbcAES128 iv consistentKey padded 247 248 poValidate :: BS.ByteString -> Bool 249 poValidate bs = case CU.unpkcs7 (AES.decryptCbcAES128 consistentKey bs) of 250 Nothing -> False 251 Just _ -> True 252 253 paddingOracleAttack :: BS.ByteString -> BS.ByteString 254 paddingOracleAttack cip = loop mempty (reverse (CU.chunks 16 cip)) where 255 loop !acc rcs = case rcs of 256 [] -> acc 257 (h:[]) -> acc 258 (h:r@(i:t)) -> loop (poAttackBlock i h <> acc) r 259 260 poAttackBlock :: BS.ByteString -> BS.ByteString -> BS.ByteString 261 poAttackBlock tol tar = byte tol tar mempty mempty where 262 byte c0' c1 p1 i1 = case BS.unsnoc c0' of 263 Nothing -> p1 264 Just (t, h) -> 265 let ncb = next t h i1 c1 266 il = BS.length i1 267 pb = fromIntegral il + 1 268 nib = ncb `B.xor` pb 269 npb = BS.index tol (15 - fromIntegral il) `B.xor` nib 270 in byte t c1 (BS.cons npb p1) (BS.cons nib i1) 271 272 next bs b i1 c1 = 273 let l = fromIntegral (BS.length i1) + 1 274 c = BS.map (B.xor l) i1 275 c0' = BS.snoc bs b <> c 276 277 roll byt = 278 let c0' = BS.snoc bs byt <> c 279 in if poValidate (c0' <> c1) && cert bs (BS.cons byt c <> c1) 280 then byt 281 else roll (byt + 1) 282 283 in roll b 284 285 cert c0' etc = case BS.unsnoc c0' of 286 Nothing -> True 287 Just (bs, b) 288 | poValidate (BS.snoc bs (b + 1) <> etc) -> True 289 | otherwise -> False 290 291 -- CTR reused-nonce 292 293 rninputs :: [BS.ByteString] 294 rninputs = [ 295 "SSBoYXZlIG1ldCB0aGVtIGF0IGNsb3NlIG9mIGRheQ==" 296 , "Q29taW5nIHdpdGggdml2aWQgZmFjZXM=" 297 , "RnJvbSBjb3VudGVyIG9yIGRlc2sgYW1vbmcgZ3JleQ==" 298 , "RWlnaHRlZW50aC1jZW50dXJ5IGhvdXNlcy4=" 299 , "SSBoYXZlIHBhc3NlZCB3aXRoIGEgbm9kIG9mIHRoZSBoZWFk" 300 , "T3IgcG9saXRlIG1lYW5pbmdsZXNzIHdvcmRzLA==" 301 , "T3IgaGF2ZSBsaW5nZXJlZCBhd2hpbGUgYW5kIHNhaWQ=" 302 , "UG9saXRlIG1lYW5pbmdsZXNzIHdvcmRzLA==" 303 , "QW5kIHRob3VnaHQgYmVmb3JlIEkgaGFkIGRvbmU=" 304 , "T2YgYSBtb2NraW5nIHRhbGUgb3IgYSBnaWJl" 305 , "VG8gcGxlYXNlIGEgY29tcGFuaW9u" 306 , "QXJvdW5kIHRoZSBmaXJlIGF0IHRoZSBjbHViLA==" 307 , "QmVpbmcgY2VydGFpbiB0aGF0IHRoZXkgYW5kIEk=" 308 , "QnV0IGxpdmVkIHdoZXJlIG1vdGxleSBpcyB3b3JuOg==" 309 , "QWxsIGNoYW5nZWQsIGNoYW5nZWQgdXR0ZXJseTo=" 310 , "QSB0ZXJyaWJsZSBiZWF1dHkgaXMgYm9ybi4=" 311 , "VGhhdCB3b21hbidzIGRheXMgd2VyZSBzcGVudA==" 312 , "SW4gaWdub3JhbnQgZ29vZCB3aWxsLA==" 313 , "SGVyIG5pZ2h0cyBpbiBhcmd1bWVudA==" 314 , "VW50aWwgaGVyIHZvaWNlIGdyZXcgc2hyaWxsLg==" 315 , "V2hhdCB2b2ljZSBtb3JlIHN3ZWV0IHRoYW4gaGVycw==" 316 , "V2hlbiB5b3VuZyBhbmQgYmVhdXRpZnVsLA==" 317 , "U2hlIHJvZGUgdG8gaGFycmllcnM/" 318 , "VGhpcyBtYW4gaGFkIGtlcHQgYSBzY2hvb2w=" 319 , "QW5kIHJvZGUgb3VyIHdpbmdlZCBob3JzZS4=" 320 , "VGhpcyBvdGhlciBoaXMgaGVscGVyIGFuZCBmcmllbmQ=" 321 , "V2FzIGNvbWluZyBpbnRvIGhpcyBmb3JjZTs=" 322 , "SGUgbWlnaHQgaGF2ZSB3b24gZmFtZSBpbiB0aGUgZW5kLA==" 323 , "U28gc2Vuc2l0aXZlIGhpcyBuYXR1cmUgc2VlbWVkLA==" 324 , "U28gZGFyaW5nIGFuZCBzd2VldCBoaXMgdGhvdWdodC4=" 325 , "VGhpcyBvdGhlciBtYW4gSSBoYWQgZHJlYW1lZA==" 326 , "QSBkcnVua2VuLCB2YWluLWdsb3Jpb3VzIGxvdXQu" 327 , "SGUgaGFkIGRvbmUgbW9zdCBiaXR0ZXIgd3Jvbmc=" 328 , "VG8gc29tZSB3aG8gYXJlIG5lYXIgbXkgaGVhcnQs" 329 , "WWV0IEkgbnVtYmVyIGhpbSBpbiB0aGUgc29uZzs=" 330 , "SGUsIHRvbywgaGFzIHJlc2lnbmVkIGhpcyBwYXJ0" 331 , "SW4gdGhlIGNhc3VhbCBjb21lZHk7" 332 , "SGUsIHRvbywgaGFzIGJlZW4gY2hhbmdlZCBpbiBoaXMgdHVybiw=" 333 , "VHJhbnNmb3JtZWQgdXR0ZXJseTo=" 334 , "QSB0ZXJyaWJsZSBiZWF1dHkgaXMgYm9ybi4=" 335 ] 336 337 rncrypted :: [BS.ByteString] 338 rncrypted = fmap (enc . B64.decodeBase64Lenient) rninputs where 339 enc = AES.encryptCtrAES128 0 "YELLOW SUBMARINE" 340 341 rnscrypted :: [BS.ByteString] 342 rnscrypted = fmap (BS.take 16) rncrypted 343 344 rnrotated :: [BS.ByteString] 345 rnrotated = CU.rotate 16 (BS.concat rnscrypted) 346 347 -- FIXME replace Cryptopals.Util.best with this? 348 rnBest :: BS.ByteString -> (Word8, Double, BS.ByteString) 349 rnBest s = loop (0, 1 / 0, s) 0 where 350 loop acc@(_, asc, _) b 351 | b == 255 = acc 352 | otherwise = 353 let xo = CU.singleByteXor b s 354 in case CU.scoreAlt xo of 355 Nothing -> loop acc (succ b) 356 Just sc 357 | sc < asc -> loop (b, sc, xo) (succ b) 358 | otherwise -> loop acc (succ b) 359 360 -- CBC key recovery w/IV=key 361 362 -- Usually we include the IV with the ciphertext, but that won't fly here 363 -- as it would very obviously expose the key. Instead let's omit the IV 364 -- in the ciphertext: 365 366 ivlEncryptCbcAES128 367 :: BS.ByteString -> BS.ByteString -> BS.ByteString 368 ivlEncryptCbcAES128 key plaintext = loop key mempty (BS.splitAt 16 plaintext) 369 where 370 loop las !acc (b, bs) = 371 let xed = CU.fixedXor las b 372 enc = AES.encryptEcbAES128 key xed 373 nacc = acc <> enc 374 in if BS.null bs 375 then nacc 376 else loop enc nacc (BS.splitAt 16 bs) 377 378 ivlDecryptCbcAES128 379 :: BS.ByteString -> BS.ByteString -> BS.ByteString 380 ivlDecryptCbcAES128 key ciphertext = 381 let (iv, cip) = BS.splitAt 16 (key <> ciphertext) 382 in loop iv mempty (BS.splitAt 16 cip) 383 where 384 loop !las !acc (b, bs) = 385 let dec = AES.decryptEcbAES128 key b 386 nacc = acc <> CU.fixedXor dec las 387 niv = b 388 in if BS.null bs 389 then nacc 390 else loop b nacc (BS.splitAt 16 bs) 391 392 ivlVerifier :: BS.ByteString -> Either BS.ByteString Bool 393 ivlVerifier cip = loop pay where 394 pay = ivlDecryptCbcAES128 consistentKey cip 395 loop p = case BS.uncons p of 396 Nothing -> pure True 397 Just (b, bs) 398 | b > 127 -> Left pay 399 | otherwise -> loop bs