Hackvent 2019: Day 8
Challenge
HV19.08 SmileNcryptor 4.0
Introduction
You hacked into the system of very-secure-shopping.com and you found a SQL-Dump with $$-creditcards numbers. As a good hacker you inform the company from which you got the dump. The managers tell you that they don't worry, because the data is encrypted.
Dump-File: dump.zip
Goal
Analyze the "Encryption"-method and try to decrypt the flag.
Dump-File: HV19-Day8-dump.zip
Solution
We download the zip file and extract the dump.sql file within.
It contains the database schema for some credit cards as well as our flag.
First we look at the flag insert statement:
INSERT INTO `flags` VALUES (1,'HV19{',':)SlQRUPXWVo\Vuv_n_\ajjce','}');
The prefix and postfix of the flag is stored explicitly in different fields. We then see our first smile ciphertext which looks like this: :)SlQRUPXWVo\Vuv_n_\ajjce
We don't know the plaintext that generated this ciphertext so there isn't much information we can extract from this. We note that it appears as if all smiley ciphertexts begin with an ASCII smiley face :)
Next we look at the credit card insert statements:
INSERT INTO `creditcards` VALUES
(1,'Sirius Black',':)QVXSZUVY\ZYYZ[a','12/2020'),
(2,'Hermione Granger',':)QOUW[VT^VY]bZ_','04/2021'),
(3,'Draco Malfoy',':)SPPVSSYVV\YY_\\]','05/2020'),
(4,'Severus Snape',':)RPQRSTUVWXYZ[\]^','10/2020'),
(5,'Ron Weasley',':)QTVWRSVUXW[_Z`\b','11/2020');
Again we see some smile ciphertext which is in the cc_number field which indicates that the plaintext for these ciphertexts should be valid credit card numbers (why would invalid numbers be stored on a shopping website).
At this point we do some research on credit card number formats and standards. See Payment card number on Wikipedia.
In particular, we wanted to know how long various credit card numbers can be, if they have any hard coded values and how to check if a credit card number is valid.
We find out the following:
American Express has 15 digits and starts with 3
Diners Club has 14-19 digits and starts with 3
Mastercard has 16 digits and starts with 5
Visa has 16 digits and starts with 4
JCB has 16-19 chars and starts with 3528–3589
When inspecting the lengths of our credit card numbers with the smiley face :)
removed, we observe the following lengths:
A) 15: QVXSZUVY\ZYYZ[a
B) 14: QOUW[VT^VY]bZ_
C) 16: SPPVSSYVV\YY_\\]
D) 16: RPQRSTUVWXYZ[\]^
E) 16: QTVWRSVUXW[_Z`\b
Naturally, as these lengths match the required lengths of credit card numbers produced by various vendors, we can assume that a 1 to 1 mapping exists between the ciphertext and plaintext. This is a good indicator that an ASCII rotation/shift cipher should be used. We match each credit card vendor to the ciphertext based on its length. Ciphertext A is matched to American Express and B to Diners Club. For the 16 digit ciphertexts, we don't know exactly which vendor to use. However, as ciphertext E begins with Q (just like A and B) which means that logically Q maps to the digit 3
. We take a guess here and assume that the other two 16 digit ciphertexts belong to Mastercard and Visa respectively. As R comes after Q in the alphabet we guess that R maps to the digit 4
and S maps to the digit 5
.
From this we guess we need to do an ASCII rotation which shifts Q (ASCII 81) to equal 3 (ASCII 51) which is a shift of -30 dec.
We write a script to perform this ASCII rotation decipher on our ciphertexts, throwing the final result in a credit card checksum validator function but the results are not valid credit card numbers.
Finally, we notice that our ciphertexts creep up the ASCII range as we move through the string character by character even though the number space for the credit cards is always 0-9 (digits). This implies the ASCII rotation is offset after each character is encoded or decoded. Ciphertext E (JCB) helped us the most here as we know it has to start with the digits 35
. Based on our initial mapping, this suggested the offset simply grew by 1 each character.
After adding this offset to our script, our computed plain texts validate as valid credit card numbers!
Python script:
# Hackvent Day 8
# Mo Beigi (https://mobeigi.com)
INITIAL_ASCII_ROTATE_OFFSET = 30 # Initial offset
def smile_decode(ciphertext):
plaintext = ''
incremental_offset = 0
for c in ciphertext:
shifted_val = ord(c) - INITIAL_ASCII_ROTATE_OFFSET - incremental_offset
plaintext += chr(shifted_val)
incremental_offset += 1
return plaintext
# Source: https://dev.to/anuragrana/python-script-validating-credit-card-number-luhn-s-algorithm-2f7c
def sum_digits(digit):
if digit < 10:
return digit
else:
sum = (digit % 10) + (digit // 10)
return sum
def validate(cc_num):
# reverse the credit card number
cc_num = cc_num[::-1]
# convert to integer list
cc_num = [int(x) for x in cc_num]
# double every second digit
doubled_second_digit_list = list()
digits = list(enumerate(cc_num, start=1))
for index, digit in digits:
if index % 2 == 0:
doubled_second_digit_list.append(digit * 2)
else:
doubled_second_digit_list.append(digit)
# add the digits if any number is more than 9
doubled_second_digit_list = [sum_digits(x) for x in doubled_second_digit_list]
# sum all digits
sum_of_digits = sum(doubled_second_digit_list)
# return True or False
return sum_of_digits % 10 == 0
# Remember to escape backslashes for python here!
credit_cards = [
{"name": "A) American Express", "ciphertext": 'QVXSZUVY\ZYYZ[a'},
{"name": "B) Diners Club", "ciphertext": 'QOUW[VT^VY]bZ_'},
{"name": "C) Mastercard", "ciphertext": 'SPPVSSYVV\YY_\\\\]'},
{"name": "D) VISA", "ciphertext": 'RPQRSTUVWXYZ[\\]^'},
{"name": "E) JCB", "ciphertext": 'QTVWRSVUXW[_Z`\\b'},
]
# Credit Card numbers
for cc in credit_cards:
cc_num = smile_decode(cc['ciphertext'])
assert(validate(cc_num))
print(f"{cc['name']} with ciphertext {cc['ciphertext']} was decoded to: {cc_num}")
# Final flag
print("\nFinal flag is: HV19{" + smile_decode('SlQRUPXWVo\\Vuv_n_\\ajjce') + "}")
Output:
C:\Users\Mo\Desktop>py -3 ./day8.py
A) American Express with ciphertext QVXSZUVY\ZYYZ[a was decoded to: 378282246310005
B) Diners Club with ciphertext QOUW[VT^VY]bZ_ was decoded to: 30569309025904
C) Mastercard with ciphertext SPPVSSYVV\YY_\\] was decoded to: 5105105105105100
D) VISA with ciphertext RPQRSTUVWXYZ[\]^ was decoded to: 4111111111111111
E) JCB with ciphertext QTVWRSVUXW[_Z`\b was decoded to: 3566002020360505
Final flag is: HV19{5M113-420H4-KK3A1-19801}
Flag:
HV19{5M113-420H4-KK3A1-19801}