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.