After connecting via netcat we got more information:
I'll provide you a PAN, date as MMYY, CSC, and a CVV.
You just need to reply with 1 if its valid and 0 if it's invalid.
I'm counting on you. And be sure to keep track of your answers so we don't need to check these again.
Each entry is in the format PAN: X, date: X, code: X, cvv: X
for example PAN: 7894591937750079490, date: 1241, code: 572, cvv: 231
On wikipedia we learn that PAN can be checked using Luhn algorithm
However, this is not enough for us. We have some key dae55498c432545826fb153885bcb06b
in the description. At first, I did not understand why and only verified PAN, and date (no need to check the date after all) but did not address CSC and CVV at all. I started searching and came across this article on LinkedIn which describes CVV calculation. I implemented it and it worked exactly as it should.
To calculate the CVV, we need to:
- Concatenate: Join the PAN, expiry date, and service code together and add zeros to the end until it forms a 16-byte (32 character) string.
- Split Key: Divide the key obtained from step 1 into two equal parts.
- Split CVV Key: Divide the 16-Byte (32 Hexadecimal) CVV Key into two equal parts.
- Encrypt Block 1: Use the DES (Data Encryption Standard) algorithm to encrypt the first block from Step 2 using the first block from Step 3 as the key.
- XOR: Perform the XOR (exclusive OR) operation between the result of Step 4 and the second block from Step 2.
- Encrypt Result: Encrypt the result from Step 5 using the first block from Step 3 as the key, again using the DES algorithm.
- Decrypt Result: Decrypt the result from Step 6 using the second block from Step 3 as the key, using the DES algorithm.
- Encrypt Again: Encrypt the result from Step 7 one more time using the first block from Step 3 as the key, using the DES algorithm.
- Extract Numerics: Extract all the numeric digits from the result of Step 8.
- Get CVV: Take the first three numbers from the result of Step 9 as the calculated CVV for the card.
So we just need to check that PAN is luhn and that our CVV calculation returns the same CVV as the socket returns.
Then I just saved the answer for each card, which resulted in a binary string and converted it to ascii.
Full implementation in Python
from Crypto.Cipher import DES
import binascii
import socket
import re
def check_luhn(number):
checksum = int(number[-1])
total = 0
for i in range(len(number) - 2, -1, -1):
sum = 0
digit = int(number[i])
if i % 2 == len(number) % 2: # right to left every odd digit
digit = digit * 2
sum = digit // 10 + digit % 10
total += sum
return (total % 10 != 0 and 10 - total % 10 == checksum) or (
total % 10 == 0 and checksum == 0
)
CVV_KEY = "dae55498c432545826fb153885bcb06b"
CVV_KEY_FIRST = CVV_KEY[:16]
CVV_KEY_SECOND = CVV_KEY[16:]
def des_encrypt(key, message):
key = binascii.unhexlify(key)
message = binascii.unhexlify(message)
cipher = DES.new(key, DES.MODE_ECB)
return cipher.encrypt(message).hex().upper()
def des_decrypt(key, message):
key = binascii.unhexlify(key)
message = binascii.unhexlify(message)
cipher = DES.new(key, DES.MODE_ECB)
return cipher.decrypt(message).hex().upper()
def all_numbers(message):
return "".join([c for c in message if c.isdigit()])
def hex_xor(a, b):
result = hex(int(a, 16) ^ int(b, 16))[2:]
return result.zfill(max(len(a), len(b)))
def is_cvv_valid(card: str, date: str, csc: str, cvv: str) -> bool:
pan_con = card
pan_con += date
pan_con += csc
pan_con += "0" * (32 - len(pan_con))
block1 = pan_con[:16]
block2 = pan_con[16:]
step4 = des_encrypt(CVV_KEY_FIRST, block1)
step5 = hex_xor(step4, block2)
step6 = des_encrypt(CVV_KEY_FIRST, step5)
step7 = des_decrypt(CVV_KEY_SECOND, step6)
step8 = des_encrypt(CVV_KEY_FIRST, step7)
step9 = all_numbers(step8)[:3]
return step9 == cvv
def is_valid(card: str, date: str, csc: str, cvv: str) -> bool:
if not check_luhn(card):
return False
if not is_cvv_valid(card, date, csc, cvv):
return False
return True
s = socket.socket()
port = 8625
s.connect(("puffer.utctf.live", port))
answers = ""
while True:
data = s.recv(1024).decode("utf-8")
match = re.search(r"PAN: (\d+), date: (\d+), code: (\d+), cvv: (\d+)", data)
if not data:
break
if match:
card, date, csc, cvv = match.groups()
validity = is_valid(card, date, csc, cvv)
if validity:
s.sendall(b"1\n")
else:
s.sendall(b"0\n")
answers += "1" if validity else "0" + ""
print(
f"Card: {card}, Date: {date}, CSC: {csc}, CVV: {cvv}, Validity: {validity}"
)
def bin2str(bin):
return "".join([chr(int(bin[i : i + 8], 2)) for i in range(0, len(bin), 8)])
print(f"Flag is {bin2str(answers)}")
Expected flag
utflag{hope_none_of_those_were_yours_lol}