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")


+ Recent posts