In LogonRequest, there are three things we know: Plaintext (Nonce | U ), IV, and Ciphertext.
We can manipulate IV so that the first 16 bytes of ticket is decrypted to whatever string we want.
challengeCookie: Nonce (8B) | User (null-terminated) | Timestamp
ticket: Identity | Timestamp
Set User to be 'ups":["admin"]}' prepended by 8 dummy characters, and we will modify IV so the first 16 bytes of ticket is decrypted to be '{"user":"x","gro' (without single quotes).
import argparse
import sys
import socket
from Crypto.Hash import SHA256
from Crypto.Cipher import AES
import time
from struct import pack
from base64 import b64encode, b64decode
from pwn import *
def readNullTerminatedString(f):
buf = b''
while True:
if len(buf) > 1 << 20:
raise Exception("Overly long input")
c = f.read(1)
if len(c) == 0:
raise Exception("End of stream reached")
if ord(c) == 0: # Indicates NULL termination of a UTF-8 string.
break
buf += c
return unicode(buf, encoding="utf-8", errors="strict")
def toNullTerminatedUtf8(s):
return unicode(s).encode("utf-8") + "\x00"
def getCurrentTimestamp():
return time.time() * 1000
class Client:
nonceLengthInBytes = 8
def __init__(self, host, port, username):
self.username = username
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._socket.setblocking(1)
self._socket.connect((host, port))
self._f = self._socket.makefile("rw")
def close():
self._f.close()
self._socket.close()
def execute(self, command):
self._sendMessage_Command(command)
return self._expectMessage_CommandResult()
def _sendMessage_LogonRequest(self):
self._f.write("\x01")
self._f.write(toNullTerminatedUtf8(self.username))
self._f.flush()
def _expectMessage_LogonChallenge(self):
self._expectMessageType(0x02)
nonce = self._readBytes(self.nonceLengthInBytes)
challengeCookie = self._expectString()
return (nonce, challengeCookie)
def _sendMessage_Command(self, command):
self._f.write("\x06")
self._f.write(toNullTerminatedUtf8(self.ticket))
self._f.write(toNullTerminatedUtf8(command))
self._f.flush()
def _expectMessage_CommandResult(self):
messageType = self._readMessageType()
if messageType == 0x07:
result = self._expectString()
return result
elif messageType == 0x05:
sys.stderr.write("Unauthorized\n")
exit(1)
else:
raise Exception("Unexpected message type: 0x%02x" % messageType)
def _readMessageType(self):
messageTypeByte = self._readBytes(1)
if (len(messageTypeByte) == 0):
raise Exception("Server has disconnected")
return ord(messageTypeByte)
def _expectMessageType(self, expectedMessageType):
messageType = self._readMessageType()
if messageType != expectedMessageType:
raise Exception("Unexpected message type: 0x%02x" % messageType)
def _readBytes(self, nBytes):
result = self._f.read(nBytes)
if len(result) != nBytes:
raise Exception("Connection was closed")
return result
def _expectString(self):
buf = b''
while True:
if len(buf) > 1 << 20:
raise Exception("Overly long input")
c = self._f.read(1)
if len(c) == 0:
raise Exception("End of stream reached")
if ord(c[0]) == 0: # Indicates NULL termination of a UTF-8 string.
break
buf += c
return unicode(buf, encoding="utf-8", errors="strict")
def pad(data):
result = data
nPadBytes = AES.block_size - len(data) % AES.block_size
for i in range(0, nPadBytes):
result += chr(nPadBytes)
return result
def s_xor(s1, s2):
assert len(s1) == len(s2)
return ''.join(map(chr, [ord(a) ^ ord(b) for (a, b) in zip(s1, s2)]))
def get_intermediary(p, c, iv):
return s_xor(iv + c[:-AES.block_size], p)
if __name__ == "__main__":
client = Client('127.0.0.1', int(sys.argv[1]), 'X'*8 + 'ups":["admin"]}')
client._sendMessage_LogonRequest()
(nonce, challengeCookie) = client._expectMessage_LogonChallenge()
timestamp = getCurrentTimestamp()
p = pad(nonce + toNullTerminatedUtf8(client.username) + p64(timestamp))
c = b64decode(challengeCookie)
iv, c = c[:AES.block_size], c[AES.block_size:]
print 'P:',p.encode('hex')
print 'C:',c.encode('hex')
print 'IV:',iv.encode('hex')
inter = get_intermediary(p, c, iv)
print 'Inter:',inter.encode('hex')
new_iv = s_xor(inter[:AES.block_size], '{"user":"x","gro')
print 'new_IV:',new_iv.encode('hex')
client.ticket = b64encode(new_iv + c)
print 'ticket:',(client.ticket).encode('hex')
print client.execute("getflag")