# Potentially Eazzzy
We are given a python file. The purpose of this script is to verify licenses. You can view it [here](potentially-eazzzy.py).
Upon inspection of the script, it looks like it uses the email in the license check. That makes sense. That way, no one can use the same license with two separate emails.
If we want to get anywhere in this challenge, we'll have to reverse engineer the script.
```pythondef main():    print("Welcome to Flag Generator 5000")    print()    print("Improving the speed quality of CTF solves since 2020")    print()    print("You'll need to have your email address and registration key ready.")    print("Please note the support hotline is closed for COVID-19 and will be")    print("unavailable until further notice.")    print()
    email = input("Please enter your email address: ")    key = input("Please enter your key: ")```
First off, it greets the user. Then, it asks the user for the email and flag and stores those inputs as strings.
```pythonif validate(email, key):    print_flag()else:    print("License not valid. Please contact support.")```
It then validates the license by calling `validate(email, key)`, and then prints the flag if the function returns `True`. So far so good.
Let's look at the `validate()` function.
```pythondef validate(email, key):    email = email.strip()    key = key.strip()```
First, it removes tailing spaces off of the email and key.
```pythonif len(key) != 32:    return False```
It then checks if the key is 32 characters in length. If the key isn't, the function returns `False`, which is not what we want. In order to print the flag, the function has to return `True`. Therefore, the key has to be 32 characters in length.
```pythonemail = email[:31].ljust(31, "*")email += "*"```
It then truncates the email down to 31 characters, and if the email is less than 31 characters, it fills the rest with stars. Finally, it adds one last star to the end of the string for a total of 32 characters.
```pythonfor c in itertools.chain(email, key):    if c not in ALPHABET:        return False```
The value of `ALPHABET` is defined at the beginning of the script. It contains a list of characters ranging from 0x2a-0x7a.
```pythonALPHABET = [chr(i) for i in range(ord("*"), ord("z")+1)]```
So this part checks if both the email and key are "alphabetical", which by "alphabetical" I really mean in the ASCII range 0x2a-0x7a.
After that, there are a lot of constraints the email and license must obey.
```pythonif email.count("@") != 1:    return False```
The email has to have at least one @ sign.
```pythonif key[0] != "Z":    return False```
The first character of the license must be a Z.
```pythondotcount = email.count(".")if dotcount < 0 or dotcount >= len(ALPHABET):    return False```
The email has to have between 0 and 80 periods. (don't ask me why, as the email is already only 32 characters)
I implemented all this in a [script I wrote](sol.py) to generate a license for me, so I'm not doing it by hand or anything like that. More on that later.
Then comes the crazy checks. I swear, whoever wrote this used a script to write this part:
```pythonif a(dotcount) != o(key[1]):    return False
if o(key[3]) != a(o(key[1])%30 + o(key[2])%30) + 5:    return False
if o(key[2]) != a(indexes(email, "*") + 7):    return False
if o(key[4]) != a(sum(o(i) for i in email)%60 + o(key[5])):    return False
if o(key[5]) != a(o(key[3]) + 52):    return False
if o(key[6]) != a((o(key[7])%8)*2):    return False
if o(key[7]) != a(o(key[1]) + o(key[2]) - o(key[3])):    return False
if o(key[8]) != a((o(key[6])%16) / 2):    return False
if o(key[9]) != a(o(key[6]) + o(key[4]) + o(key[8]) - 4):    return False
if o(key[10]) != a((o(key[1])%2) * 8 + o(key[2]) % 3 + o(key[3]) % 4):    return False
if not m(email[3], key[11], key[12], 8):    return Falseif not m(email[7], key[13], key[4], 18):    return Falseif not m(email[9], key[14], key[3], 23):    return Falseif not m(email[10], key[15], key[10], 3):    return Falseif not m(email[11], key[13], key[16], 792):    return Falseif not m(email[12], key[17], key[4], email.count("d")):    return Falseif not m(email[13], key[18], key[7], email.count("a")):    return Falseif not m(email[14], key[19], key[8], email.count("w")):    return Falseif not m(email[15], key[20], key[1], email.count("g")):    return Falseif not m(email[16], email[17], key[21], email.count("s")):    return Falseif not m(email[18], email[19], key[22], email.count("m")):    return Falseif not m(email[20], key[23], key[17], 9):    return Falseif not m(email[21], key[24], key[13], 41):    return Falseif not m(email[22], key[25], key[10], 3):    return Falseif not m(email[23], key[26], email[14], email.count("1")):    return Falseif not m(email[24], email[25], key[27], email.count("*")):    return Falseif not m(email[26], email[27], key[28], 7):    return Falseif not m(email[28], email[29], key[29], 2):    return Falseif not m(email[30], key[30], email[18], 4):    return Falseif not m(email[31], key[31], email[4], 7):    return False
return True```Either that, or the author took a lot of time and effort writing this challenge!
Anyway, there are three functions you need to know about, defined at the beginning of the file:
```pythona = lambda c: ord(ALPHABET[0]) + (c % len(ALPHABET))
o = lambda c: ord(c)
oa = lambda c: a(o(c))```
They are all lambda functions that take one parameter.
`o('Z')` is just short for `ord('Z')`
`a(126)` is another function that is used to convert any number to a printable char, by getting the modulo of your input and the length of `ALPHABET`, and then adding the ord of the first character in `ALPHABET`. It returns really the `ord` of the printable char, and is used to compare to characters in the license, to check if the license is valid.
And finally, `oa()` is just a combination of both functions.
First things first:
```pythonif a(dotcount) != o(key[1]):    return False```
This is saying that the `a()` of the email dot count has to be equal to the ord of the second letter of the license.
While I was examining the inner workings of this program, I was making my own script that I can use to generate a license, given an email. You can see it [here](sol.py). I created an array called `key` to store all the key codes of the license.
```pythonkey = [ord('A')] * 32 ## contains ascii codes for the key, the key is 32 characters long.```
I decided to store the characters this way because strings are immutable, and I can change the individual characters of the license as I please now.
So now, I can edit the second character of the license using this check. Knowing that the second character, represented as an integer, has to be equal to `a(dotcount)`, I can simply do this:
```pythonkey[1] = a(dotcount)```
And now this requirement:
```pythonif a(dotcount) != o(key[1]):    return False```
...Is now satisfied.
Now, I can't do the same thing for the next one:
```pythonif o(key[3]) != a(o(key[1])%30 + o(key[2])%30) + 5:    return False```
...Because the value of `key[3]` depends on the value of `key[2]`, so I have to get `key[2]` first, which is pretty straight forward.
```pythonif o(key[2]) != a(indexes(email, "*") + 7):    return False```
The next one has the check with `key[2]`, so now we can get `key[2]` with this code:
```pythonkey[2] = a(indexes(email, "*") + 7)```
Now that we have `key[2]`, we can now get `key[3]`
```pythonkey[3] = a(key[1]%30 + key[2]%30) + 5```
And we can simply keep going by doing the same steps we did before. Go to the next piece of code. If it depends on another part of the license, calculate that part first.
```pythonif o(key[4]) != a(sum(o(i) for i in email)%60 + o(key[5])):    return False
if o(key[5]) != a(o(key[3]) + 52):    return False```
`key[4]` depends on `key[5]`, and `key[5]` depends on `key[3]`. We already have `key[3]`, so we can go ahead and get `key[5]`. Then we can get `key[4]`
```pythonkey[5] = a(key[3] + 52) ## the order of key calculation matters, because the value of key[4] depends on key[5].
key[4] = a(sum(o(i) for i in email)%60 + key[5])```
It keeps going like that, until `key[10]`
```pythonkey[7] = a(key[1] + key[2] - key[3])
key[6] = a((key[7]%8)*2)
key[8] = a((key[6]%16) / 2)
key[9] = a(key[6] + key[4] + key[8] - 4)
key[10] = a((key[1])%2 * 8 + key[2] % 3 + key[3] % 4)```
Then it starts getting more complex. We are introduced to a new function `m()`
```pythondef m(one, two, three, four):    d = len(ALPHABET)//2    s = ord(ALPHABET[0])    s1, s2, s3 = o(one) - s, o(two) - s, o(three) - s    return sum([s1, s2, s3]) % d == four % d```
It takes 4 parameters. I edited the function in my script to make it easier to understand.
```pythondef m(one, two, three, four):    d = 40    s = 42    s1, s2, s3 = one - 42, two - 42, three - 42    return sum([s1, s2, s3]) % 40 == four % 40```
It takes parameters 1 to 3, and subtracts it by 42. It then checks if the modulus of the sum of those three numbers we just calculated to 40 is equal to the fourth parameter applied with the same modulus.
The question is, how do we reverse engineer this?
Let's look at an example on how it's used:
```pythonif not m(email[3], key[11], key[12], 8):    return False```
In order for this check to pass, the function being called, `m()`, has to return `True`
After some trial and error, I got this:
```pythondef l(two, three, four):    one = ((sum([two, three]) + (-42 * 3)) % 40) - (four % 40)    while one > 0:        one = one - 40    one = -one    while one < 0x2a:        one += 40    return one```
I was able to create a function that basically gets `one` when given `two`, `three`, and `four`.
In other words, assuming you have `two`, `three`, and `four`, `m(l(two, three, four), two, three, four)` will __always__ return `True`
Also, remember that all characters have to be in the ASCII range `0x2a-0x7a`, so I add 40 to each character until it is.
```pythonone = l(two, three, four)
print(m(one, two, three, four)) ## Always will return True```
I even made a [test script](test.py) to test if this will always work, and it does.
We can do the same thing we have been doing, but now we can use `l()` to do the reverse of `m()`. The order of the first three parameters doesn't matter. Cool, huh?
Here's a quick example:
```pythonif not m(email[3], key[11], key[12], 8):    return False```
Hm... We don't have either `key[11]` or `key[12]`. Let's keep going.
```pythonif not m(email[7], key[13], key[4], 18):    return False```
In my script, `email` is currently a string. Let's fix that.
```pythonoldEmail = email[:]email = []
for i in oldEmail:    email.append(ord(i))```
Now `email` is a list of character ASCII values, which is what we want.
Since we have `key[4]` and `email[7]`now, we can get `key[13]`!
```pythonkey[13] = l(email[7], key[4], 18)```
Nice! Let's keep doing this for the rest of the keys.
```pythonif not m(email[3], key[11], key[12], 8):    return Falseif not m(email[7], key[13], key[4], 18):    return Falseif not m(email[9], key[14], key[3], 23):    return Falseif not m(email[10], key[15], key[10], 3):    return Falseif not m(email[11], key[13], key[16], 792):    return Falseif not m(email[12], key[17], key[4], email.count("d")):    return Falseif not m(email[13], key[18], key[7], email.count("a")):    return Falseif not m(email[14], key[19], key[8], email.count("w")):    return Falseif not m(email[15], key[20], key[1], email.count("g")):    return Falseif not m(email[16], email[17], key[21], email.count("s")):    return Falseif not m(email[18], email[19], key[22], email.count("m")):    return Falseif not m(email[20], key[23], key[17], 9):    return Falseif not m(email[21], key[24], key[13], 41):    return Falseif not m(email[22], key[25], key[10], 3):    return Falseif not m(email[23], key[26], email[14], email.count("1")):    return Falseif not m(email[24], email[25], key[27], email.count("*")):    return Falseif not m(email[26], email[27], key[28], 7):    return Falseif not m(email[28], email[29], key[29], 2):    return Falseif not m(email[30], key[30], email[18], 4):    return Falseif not m(email[31], key[31], email[4], 7):    return False```
After we're finished, it should look something like this:
```python#    if not m(email[16], email[17], key[21], email.count("s")): ## I will just start with this#        return False
    key[21] = l(email[16], email[17], oldEmail.count('s'))
#    if not m(email[7], key[13], key[4], 18):#        return False
    key[13] = l(email[7], key[4], 18)
#    if not m(email[11], key[13], key[16], 792):#        return False
    key[16] = l(email[11], key[13], 792)
#    if not m(email[21], key[24], key[13], 41):#        return False
    key[24] = l(email[21], key[13], 41)
#    if not m(email[9], key[14], key[3], 23):#        return False
    key[14] = l(email[9], key[3], 23)
#    if not m(email[10], key[15], key[10], 3):#        return False
    key[15] = l(email[10], key[10], 3)
#    if not m(email[12], key[17], key[4], email.count("d")):#        return False
    key[17] = l(email[12], key[4], oldEmail.count('d'))
#    if not m(email[20], key[23], key[17], 9):#        return False
    key[23] = l(email[20], key[17], 9)
#    if not m(email[31], key[31], email[4], 7):#        return False
    key[31] = l(email[31], email[4], 7)
#    if not m(email[13], key[18], key[7], email.count("a")):#        return False
    key[18] = l(email[13], key[7], oldEmail.count('a'))
#    if not m(email[30], key[30], email[18], 4):#        return False1
    key[30] = l(email[30], email[18], 4)
#    if not m(email[28], email[29], key[29], 2):#        return False
    key[29] = l(email[28], email[29], 2)
#    if not m(email[26], email[27], key[28], 7):#        return False
    key[28] = l(email[26], email[27], 7)
#    if not m(email[3], key[11], key[12], 8):#        return False
    key[11] = ord('Z') # idk    key[12] = l(email[3], key[11], 8)
#    if not m(email[14], key[19], key[8], email.count("w")):#        return False
    key[19] = l(email[14], key[8], oldEmail.count('w'))
#    if not m(email[15], key[20], key[1], oldEmail.count("g")):#        return False
    key[20] = l(email[15], key[1], oldEmail.count('g'))
#    if not m(email[18], email[19], key[22], oldEmail.count("m")):#        return False
    key[22] = l(email[18], email[19], oldEmail.count('m'))
#    if not m(email[22], key[25], key[10], 3):#        return False
    key[25] = l(email[22], key[10], 3)
#    if not m(email[23], key[26], email[14], oldEmail.count("1")):#        return False
    key[26] = l(email[23], email[14], oldEmail.count('1'))
#    if not m(email[24], email[25], key[27], oldEmail.count("*")):#        return False
    key[27] = l(email[24], email[25], oldEmail.count('*'))```
And we are done!  We now reached that sweet sweet statement we've been trying to get to:
```pythonreturn True```
All that's left is to convert the license to a string, and print it to the screen!
```pythons = ''for i in key: ## convert array to string    s += chr(int(i))
return "Your key is: " + s```
And if you're interested, here's what main looks like:
```pythondef main():    email = input("What email do you want to use? ")
    print(getLicense(email))```
And we're done! That's it! All you have to do is connect to the server using netcat and get the flag!
Unfortunately, I wasn't able to get the flag. I finished it an hour after the CTF was over, but it still was a really fun challenge!
If you want to see the final solution script, it's right [here](sol.py).