Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
While reading a blog post last week, I decided to implement an ECB chosen plaintext attack.
This was also one of the challenges during ABCTF this year, so more on that below.
The theory of the attack is that if the target is using an ECB cipher mode with a secret, and you have control over the plaintext, then you can determine the “secret” being used (similar to a salt).
From an attacking perspective, this would be useful if your session cookie (or something similar) used this encrypted value for authentication or authorization.
In this case, you would just need to determine the secret being used. Once you have that, you set the cookie’s value to the encrypted value of “admin” + “secret” + padding (where necessary), or the system you are attacking’s equivalent.
So, first of all, I set out to build a server similar to c0nrad’s that would take user input, encrypt the plaintext using AES mode ECB, and return to the user the encrypted (and encoded) output.
The commented out “prepend” section will actually be used later for the ABCTF section, but I wanted to include it as this was the final code for my test server.
#!/usr/bin/python from Crypto.Cipher import AES import socket import sys import random import string blockSize = 16 encKey = "ENCRYPTIONKEY123" secret = "mys3cretP@ssword!" prepend = "" #prepend = "ENCRYPT:" chars = string.ascii_letters + string.digits + string.punctuation secret = ''.join(random.choice(chars) for _ in range(random.randint(1,1000))) def pad(input): if (len(input) % blockSize == 0): return input else: extra = blockSize - (len(input) % blockSize) output = input + "\x00" * extra return output def unpad(input): return input.rstrip("\x00") def encrypt(input): if (input is None) or (len(input) == 0): print "Input text cannot be null or empty" toEncrypt = prepend + input + secret toEncrypt = pad(toEncrypt) cipher = AES.AESCipher(encKey, AES.MODE_ECB) cipherText = cipher.encrypt(toEncrypt) return cipherText.encode("hex") def decrypt(input): if (input is None) or (len(input) == 0): print "Input text cannot be null or empty" encrypted = input.decode("hex") cipher = AES.AESCipher(encKey, AES.MODE_ECB) plainText = unpad(cipher.decrypt(encrypted)) return plainText def main(): print "SECRET LENGTH: " + str(len(secret)) print "SECRET = " + secret s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_address = ('localhost', 10000) print "\nStarting up on %s port %s" % server_address s.bind(server_address) s.listen(1) while True: connection, client_address = s.accept() try: while True: data = connection.recv(2048) if data: #print "DATA: " + data input = data.rstrip() print "INPUT: " + input print "HEX: " + input.encode("hex") encrypted = encrypt(input) print "ENCRYPTED: " + encrypted connection.send(encrypted) else: break finally: connection.close() if __name__ == "__main__": main()
To verify that my server worked, I just needed to telnet to port 10000 and send a message.
root@kali:~/ecb# telnet localhost 10000 Trying ::1... Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. MY SECRET MESSAGE 93ddd26ad133d086d16df74ef2ba96417c936e46ec4cbe142d6e69b88b4d92e515e324e836c5c43065ba47ea200d046d8c88a62410ff28ed32440c8e6c52b7cabdcac9db9db7351762e5329d2539b69e3c6ff2e87de7cb2a8b0a197d12fd1ca11e4db1cf177e90522ac7aa858882b701458849fe88bf896a798f706727982a69a42445fca22d70fc0b3f4926cdcd2ffab91facea2129e8a0d6415463d8e64b5b248413efa6b666baef257e3389e4ae3e10d2b20c53d468d7af57a9beda526eed243ac36b77c34b547df8c6c8f9f90c601b309e218639c1fac8159da652f1a4b2468dfb04804aa3a2e5bd555b9b03e539f8cad7e8e4ac4c3e907eb03f959a46e5dfbd46012044f41616d81e6bf36218c1d1863360f2ef1eb445c23d8fb155b5e22b5a0ff069bb3a5305628d90789af490758ef669f0bba00f5e3b997496337f2295f62ace663fb01b99ad94226a9991fa525910a6ba65728367f2e0f3253dc8fd7594afa25c7157f8a92281222d45799406809e0b8a1c69118e4a60c3902e8fed1a529c737f6fdc24241dd901112c8e9f5b76901d5fafc99fff7a6bd3eaf901872bdf4f522ab8991e8be973f64756489dba0f4d26c1a11d11a925115ec9779b8c14607081ec3aa44e4c4a6b231b3fc14eb1eb1eaaa42e1b59ef259b79139d3c6808889243ef70245deab1899362e155d65a4030a17250d6e9ad11c3dba7f01de01c4b28ee50f20d9903040a130eb9e7013d1921b23c1d6e5e359cc54225eb68b61d4c2b913e9ae600a172c5df936f5a0b56981edf5ebbc75cac39ad1d34e02052ac8dd48336f8219641b4df30e122e298949e5921f34f4ce86b8db46f2cf34de17f8ab5e0f960374620c79d7236ee7ac2b412155c4bfc5721484608e16c28765d8a2e61121c1a3ce8b4235853a6b38f9f620ea1e081c2bc64d4437f394e394cbfc6f9e5a4e4b97b8b1ae9f6ddfccace376b8d492aa88026e34c962d4d79d14c4778a1f4889953796b40d44db9c1215a2f
On the server side of things I saw my connection, my input, as well as the encrypted output.
root@kali:~/ecb# python ecbServer.py SECRET LENGTH: 699 SECRET = 7o|&@8Sj:D-ayGW%YA2&lk1+XeZro.@1vjdT#'/S)u\Y(B%c6+(/X=0V9I_3WES76#F`-[*1^D-r9+$|$AF;h9`)z@|{cZMI7t0^&]2;%cdckdN{nf{I~5i&3p:BR;87Hu;?}0UU}hV1SQC"|{wTU7RZC\Filc9SS0"^'nY5)U77J\>~_)}F~BcQ90Lus*[o22GwwJA9!5kcd~I7[U*N{ht*+RjkVFwRPX2g2QpE;8k$+'#H'F2h|aO?\(7mj-am/YM}]MliDI5~K$3)49z57r>thsUV"sR|)d/qyGs&'"hCD3#b}X%DNt5A'vJ"S#v7ZiO0HxDjHF^XiX{;H64IFYKVt24~y[bkh5c=96A~r}q=6wyENj!WGE(\5I`+D%W/WYU4g#yQf*|vf%4JFgG4-4HkZSL?fQpE?<^q4|,'UkBX9^UaD'n}*6wj.Q[)Sm[y)=JR^%1g4|\m=Xfi1u:U%$"w\JTL/S5)goXwHFpbZiEjI Starting up on localhost port 10000 INPUT: MY SECRET MESSAGE HEX: 4d5920534543524554204d455353414745 ENCRYPTED: 93ddd26ad133d086d16df74ef2ba96417c936e46ec4cbe142d6e69b88b4d92e515e324e836c5c43065ba47ea200d046d8c88a62410ff28ed32440c8e6c52b7cabdcac9db9db7351762e5329d2539b69e3c6ff2e87de7cb2a8b0a197d12fd1ca11e4db1cf177e90522ac7aa858882b701458849fe88bf896a798f706727982a69a42445fca22d70fc0b3f4926cdcd2ffab91facea2129e8a0d6415463d8e64b5b248413efa6b666baef257e3389e4ae3e10d2b20c53d468d7af57a9beda526eed243ac36b77c34b547df8c6c8f9f90c601b309e218639c1fac8159da652f1a4b2468dfb04804aa3a2e5bd555b9b03e539f8cad7e8e4ac4c3e907eb03f959a46e5dfbd46012044f41616d81e6bf36218c1d1863360f2ef1eb445c23d8fb155b5e22b5a0ff069bb3a5305628d90789af490758ef669f0bba00f5e3b997496337f2295f62ace663fb01b99ad94226a9991fa525910a6ba65728367f2e0f3253dc8fd7594afa25c7157f8a92281222d45799406809e0b8a1c69118e4a60c3902e8fed1a529c737f6fdc24241dd901112c8e9f5b76901d5fafc99fff7a6bd3eaf901872bdf4f522ab8991e8be973f64756489dba0f4d26c1a11d11a925115ec9779b8c14607081ec3aa44e4c4a6b231b3fc14eb1eb1eaaa42e1b59ef259b79139d3c6808889243ef70245deab1899362e155d65a4030a17250d6e9ad11c3dba7f01de01c4b28ee50f20d9903040a130eb9e7013d1921b23c1d6e5e359cc54225eb68b61d4c2b913e9ae600a172c5df936f5a0b56981edf5ebbc75cac39ad1d34e02052ac8dd48336f8219641b4df30e122e298949e5921f34f4ce86b8db46f2cf34de17f8ab5e0f960374620c79d7236ee7ac2b412155c4bfc5721484608e16c28765d8a2e61121c1a3ce8b4235853a6b38f9f620ea1e081c2bc64d4437f394e394cbfc6f9e5a4e4b97b8b1ae9f6ddfccace376b8d492aa88026e34c962d4d79d14c4778a1f4889953796b40d44db9c1215a2f
With the server in place, it was time to write my attacking script.
This script will connect to the server, calculate the length of the secret, and go about brute forcing it. For more specifics about the attack, please see the blog linked above.
#!/usr/bin/python import math import socket import sys def chunkstring(string, length): return (string[0+i:length+i] for i in range(0, len(string), length)) def roundup(x, base=10): return int(math.ceil(x / (base + 0.0))) * base s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_address = ('localhost', 10000) s.connect(server_address) try: found = False secret = "" secretLen = 0 #prependChars = "ENCRYPT:" prependChars = "" message = "A" s.sendall(message) data = s.recv(2048) output = list(chunkstring(data, 32)) initialLen = len(output) curLen = 0 while (curLen <= initialLen): message += "A" s.sendall(message) data = s.recv(2048) output = list(chunkstring(data, 32)) curLen = len(output) extra = len(message) - 1 secretLen = ((curLen - 1) * 16) - extra - len(prependChars) print "SECRETLEN: " + str(secretLen) while not found: initialBlock = "A" * (16 - len(prependChars)) fullLen = roundup(secretLen, 16) prepend = "B" * (fullLen - len(secret) - 1) message1 = initialBlock + prepend s.sendall(message1) data = s.recv(8192) initialReturn = list(chunkstring(data, 32)) #print "INITIAL: " + str(initialReturn) for i in range(33, 127): message2 = message1 + secret + chr(i) s.sendall(message2) data = s.recv(8192) oracle = list(chunkstring(data, 32)) #print "ORACLE: " + str(oracle) compareBlock = (len(prependChars + message2) / 16) - 1 #print "COMPARE = " + str(compareBlock) if oracle[compareBlock] == initialReturn[compareBlock]: secret += chr(i) #print "LENGTH: " + str(len(secret)) #print "SECRET: " + secret #print "INITIAL: " + str(initialReturn) #print "ORACLE: " + str(oracle) if len(secret) == secretLen: found = True print secret break finally: s.close()
With my attack script written, it was time to point it at my server. Within just a few seconds it had already brute forced my random 699 character secret!
root@kali:~/ecb# python ecbAttack.py SECRETLEN: 699 7o|&@8Sj:D-ayGW%YA2&lk1+XeZro.@1vjdT#'/S)u\Y(B%c6+(/X=0V9I_3WES76#F`-[*1^D-r9+$|$AF;h9`)z@|{cZMI7t0^&]2;%cdckdN{nf{I~5i&3p:BR;87Hu;?}0UU}hV1SQC"|{wTU7RZC\Filc9SS0"^'nY5)U77J\>~_)}F~BcQ90Lus*[o22GwwJA9!5kcd~I7[U*N{ht*+RjkVFwRPX2g2QpE;8k$+'#H'F2h|aO?\(7mj-am/YM}]MliDI5~K$3)49z57r>thsUV"sR|)d/qyGs&'"hCD3#b}X%DNt5A'vJ"S#v7ZiO0HxDjHF^XiX{;H64IFYKVt24~y[bkh5c=96A~r}q=6wyENj!WGE(\5I`+D%W/WYU4g#yQf*|vf%4JFgG4-4HkZSL?fQpE?<^q4|,'UkBX9^UaD'n}*6wj.Q[)Sm[y)=JR^%1g4|\m=Xfi1u:U%$"w\JTL/S5)goXwHFpbZiEjI
On the server side of things, I could see the brute force and subsequent ciphertext calculations in progress.
< ... snip ... > INPUT: AAAAAAAAAAAAAAAABBBBB7o|&@8Sj:D-ayGW%YA2&lk1+XeZro.@1vjdT#'/S)u\Y(B%c6+(/X=0V9I_3WES76#F`-[*1^D-r9+$|$AF;h9`)z@|{cZMI7t0^&]2;%cdckdN{nf{I~5i&3p:BR;87Hu;?}0UU}hV1SQC"|{wTU7RZC\Filc9SS0"^'nY5)U77J\>~_)}F~BcQ90Lus*[o22GwwJA9!5kcd~I7[U*N{ht*+RjkVFwRPX2g2QpE;8k$+'#H'F2h|aO?\(7mj-am/YM}]MliDI5~K$3)49z57r>thsUV"sR|)d/qyGs&'"hCD3#b}X%DNt5A'vJ"S#v7ZiO0HxDjHF^XiX{;H64IFYKVt24~y[bkh5c=96A~r}q=6wyENj!WGE(\5I`+D%W/WYU4g#yQf*|vf%4JFgG4-4HkZSL?fQpE?<^q4|,'UkBX9^UaD'n}*6wj.Q[)Sm[y)=JR^%1g4|\m=Xfi1u:U%$"w\JTL/S5)goXwHFpbZiEjA HEX: 414141414141414141414141414141414242424242376f7c264038536a3a442d6179475725594132266c6b312b58655a726f2e4031766a645423272f3c3f51587b7a765026507d7e6d564067353040367e22554f7e7a24506c7e397b2629256142363b464b3c5c5824636c2c315b4056735d702a3063597067252e4f3f2b4630732662314232492b674537244f6a34554c275c2e68682b233d29602b473c6a50317061217a7a43574443445b21276b7e38605b3f5d352c312569266130746e706c2f27364b274e38693361772f3a656c66257e4055532a5737613b28713e5329755c5928422563362b3c2f4277274c33702a744b3f3d4c403e282f583d305639495f3357455337362346602d5b2a315e442d72392b247c2441463b683960297a407c7b635a4d493774305e265d323b256364636b644e7b6e667b497e35692633703a42523b383748753b3f7d3055557d685631535143227c7b77545537525a435c46696c6339535330225e276e5935295537374a5c3e7e5f297d467e42635139304c75732a5b6f32324777774a413921356b63647e49375b552a4e7b68742a2b526a6b564677525058323c743c4c6c6f3c4c7a64512759714f295c683d795075454e45494d3e67325170453b386b242b272348274632687c614f3f5c28376d6a2d616d2f594d7d5d4d6c694449357e4b24332934397a3537723e74687355562273527c29642f717947732627226843443323627d5825444e74354127764a22532376375a694f304878446a48465e5869587b3b4836344946594b567432347e795b626b6835633d3936417e727d713d367779454e6a21574745285c3549602b4425572f5759553467237951662a7c766625344a466747342d34486b5a534c3f665170453f3c5e71347c2c27556b4258395e556144276e7d2a36776a2e515b29536d5b79293d4a525e253167347c5c6d3d58666931753a55252422775c4a544c2f533529676f5877484670625a69456a41 ENCRYPTED: 86dd17028adabcea56758942b23c112e339b524f7ff0165baab9057b35d4cad049ffe7fc6eb4b58f079c8b59375b8b4538f5be89f60868ad6ce77baa6abad0bb04f8c81162be5e0a1184baa4e2a35517bce436cef1899a00ebafd8d6557d544e130ecf6f814587c52597a17d958f2474428045341152f0221db9e9fe63b2f9ae4f9d8b927dcf0190d6b6417c18daf286bd3adce952277b29dc1160b57f235064a503f3a5e960d168d35cdafcda722f63f54ae5ff4e750c9567dbe89e97051bf11741e873f96b746a0a33d4495b3ee7fbfcaf3514f917c3a8d7d53ea1b9b156c4506f37e610489271c9eaafacb8aab667f7a58a489ca7783c0833299a76943fb4d555e3bc8c21d709287b940e36c490c1ec3c55d492835b75c7f52e4251da9d1376b4ed313681e6a2b291b3c7e6cf71ead956bc7a48e9a2a8a49cb177a0677b8fe9bd960dce291c9180570382be8b88dc64d742fd0504cf570e439da27131ab20a518a1b74f00b971182d1de8f43fbe1470fec2d6fcb439131c53ed4751629c300cc07ded37b5a729bb68df1c7e3382bfe97d2ec9a7b3e82f2f3c0740d45944119f68b8c52c6242f0236c0c97717423470f035e681be08fef53abfa0687f252737a12d06ca5f01dac314494bd7878b8b75bf0bd86cdb4d64441e36277e60d384db0f2af6281f4699f08f906a50d6a6bbe693ee3149dc01b72c63d421b009d6d7566dd5b3901c0d75c7026fd1cf591c4c1f12fd0c84bc96032d65aa974d63d7812d6ada3e096b42fa24a465f700e43340675118afac4c18f7510a6fbeac6023c99478d2abdc6493a9542ce88b963021ac210df4906ef80cabcd41cf904f7d85cc29573c05ddd2def0d42eba7759546eece9b9b90e924bf01947cac5627fd138854690e00aab9372f71ab292d7dfd2e4006086b1ee27556cd235a11107477dd7b74723cbd86fb29e8c7773fd617c5ba64c1a4ed7bb26ce1616bc9fcb5c58d3dcfe63629001cbd61ddd6b5b421ab4895efce351349c4c6716a7748a393def6e9b689d89332a18f9eb58383af964864565925b15fb2598f90cacaa7b521869c73bf564d9100c92badf7c09a47585a46e08914245149c4903340519925ce4fabd53ccbba62d536fc5732123d8de0988b52e82eb33e4c8e315a17ff1457eb428b28ad0e2b6a09f37e206b04312508eb28796f55810c69ae1e01d38206ad1f31769755d829fc499f24b3213d41d43cec69a8be8c85a9fa0c79f3c6f272caa532f26b7623bb7763ecd771b03a7febf81603b5e62e432d0f70934890357de757a9101f3f5f9f1ace1190a489c9f0f5d19879b68cb6cb50704633a4b4d90d4850958db7f16e53ec9907b49267e4dfe0802fdab2e7b53a04f8a3d071d13a9730b88c171b9aa1b8af15cc80880e2b80907321c633dd9f02c1d28b64b07d80ba194bcdd556cf2c0e8e20df9ae296b9c52d3f88f2b1e130271486e048b511773717f321d312692d1b5550384a1bd5935afe9b22e45e3bfff20a989516ec20ea11243ff47534f9d600bbf3df6a8b2893d6483c80dcee7fd4ee99665de3e1bde38d02f1cfb98d3d5432df73d75c4b3b6b842c9765263e58b1179c822d38c5af6773220a92956ecd2fd948ab7f0fd9d13fde73b12a6b690d31530481cac46f7856f2125b05a55099c94a848502df173c5fe6e00b37b083cbef02329bf44d704dbf77116c8a6b576730a79bf434133d9ba322c03242ba6043a4d2bd0e95a2be067310cec1337389cd47cbe4cd3e6ddbee627f09816aca1e5edb5fd1489f07fbe5dc3c1d7998aabaf23a76116335c32f07d836375152c72594687e42618906e06dac39f2fec086ab4f488f3a41c92a61e782219453db1757d7629b1354855fb7f296355d915f28b59d406f8c2b6dfc9aca301aea77ce3c63b146a3fcbcc821c8a443a1927d1af030eb554adb2225ba47d81de19c3c931ced161f448322e22583a57a8e2975d308d86bf7ea07d858339df99cdbdd2b28291b88e5 < ... snip ... >
This was actually a challenge in this year's ABCTF, so I want to touch a bit more on it.
The challenge was an AES-ECB service listening on a remote server, and the flag was the secret key being used.
While the only modifications that I needed to make to my client were regarding the prepended "Encrypt:" text, I already included them in the above code samples.
The code for the server was as follows.
# ORIGINAL - http://pastebin.com/UTkSDn4H #/usr/bin/env python from Crypto.Cipher.AES import AESCipher import SocketServer,threading,os,time import signal from secret2 import FLAG, KEY PORT = 7765 def pad(s): l = len(s) needed = 16 - (l % 16) return s + (chr(needed) * needed) def encrypt(s): return AESCipher(KEY).encrypt(pad('ENCRYPT:' + s.decode('hex') + FLAG)) class incoming(SocketServer.BaseRequestHandler): def handle(self): atfork() req = self.request def recvline(): buf = "" while not buf.endswith("\n"): buf += req.recv(1) return buf signal.alarm(5) req.sendall("Send me some hex-encoded data to encrypt:\n") data = recvline() req.sendall("Here you go:") req.sendall(encrypt(data).encode('hex') + '\n') req.close() class ReusableTCPServer(SocketServer.ForkingMixIn, SocketServer.TCPServer): pass SocketServer.TCPServer.allow_reuse_address = True server = ReusableTCPServer(("0.0.0.0", PORT), incoming) print "Server listening on port %d" % PORT server.serve_forever()
After running my modified client a short time, I recovered the secret key in use!
secret = ABCTF{p4dding_4_fun}
I was able to submit this flag and receive my 140 points. This was definitely a fun challenge, and I'm glad that it came out right around the time that I started this blog post.
The code and updates can be found in my GitHub repository.
Ray Doyle is an avid pentester/security enthusiast/beer connoisseur who has worked in IT for almost 16 years now. From building machines and the software on them, to breaking into them and tearing it all down; he’s done it all. To show for it, he has obtained an OSCE, OSCP, eCPPT, GXPN, eWPT, eWPTX, SLAE, eMAPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!
He currently serves as a Senior Staff Adversarial Engineer for Avalara, and his previous position was a Principal Penetration Testing Consultant for Secureworks.
This page contains links to products that I may receive compensation from at no additional cost to you. View my Affiliate Disclosure page here. As an Amazon Associate, I earn from qualifying purchases.