Aight

Cracking: Sublime



What
Decided to crack sublime text, not because I had to, just for fun, the message or unregistered thing never bothered me a bit.
Whenever I'll have $80 to throw around and support devs, I will, but until then, I'll have fun cracking shit.


TL;DR
I messed up bad in the beginning, made some bad assumptions, everything you need is in the first few lines of the "Process" section, and from "The Big Gay" down.


Process

Started with trying to find the (UNREGISTERED) thing. Was not in the strings.

So I went Imports, Since the window was always the same, they'd have to call SetWindowsText.

And there it was:


It had only 2 xrefs, and in the first you could find this:


Now if you notice on the left we have a bit of a loop. There's a weird ass string.
As we can see the loop xors the deference of the pointer by 0x15, and then increments the pointer.
Doing it yourself yeilds the following result:


Ez.

Now we've got 2 conditions onthere, the first checks is if (let's call it) "Somethung" & 2, and if it's 1 it does the decrypt thing.
That tells us that that dword & 2 is basically a IsUnregistered

The other alternative is one that checks Somethung & 4, and if that's true, it goes forward and calls SetWindowText, otherwise it seemingly appends "(LICENSE UPGRADE REQUIRED)", telling us that dword & 4 is basically IsLicenseUpgradNeeded.

Maybe this'll be easier to visualize:
if(license_info & 2)
showUNREGISTERED();
else if(license_info & 4)
showLICENSEUPGRADEREQUIRED();
else GoOn();


Now we could just jmp the two conditions, be set and go on to crack the save message, but we preferably want to make the program itself think it's "registered", so anything else having the free version might trigger will go away.

So I trace it back. See what sets that value. Somwhere along the way I find this:


So now we know that that struct+1240+1 contains a byte/bool that seems to be UpgradeRequired. And also that struct+1240 contains a byte/bool for IsRegistered.

Again, easier to visualize:
License_Info__ = 0;
if(!Something_License_->isRegistered)
License_Info__ = 2
if(!Something_License_->UpgradeRequired)
License_Info__ |= 4;


Now, that Something_License_ is the first param to that function (rcx). Function has a shit tonne of xrefs tho (which is not at all bad, just means a lot of code uses it).
So now we go dynamic.

RCX doesn't seem to change between calls. Great! So let's see what accesses it.


Bit of a problem.
So those 204s you see, are when the cursor blinks, those 826s are when you move the mouse. So this seems to be some display class.
Since the class was passed as first param and this is display, chances are there's a vtable. Let's see.


Great, so we can go back to static.
So that 00007FF741F64060 - sublime_text.exe is 524060:


And since I coded a PE dumper with rtti, we can check what that actually is:


So edit_window with estimated 55 virtual functions. Doesn't help a lot since we already deduced that it was some kind of display stuff.

Okay, if we wanna find the license stuff, we gotta see how that edit_window+1240 is set. Now I find no reason as to why that would be set twice (besides someone actually activating it), so maybe check some kind of constructor.
Because this is has a vtable, identifying a constructor is a matter of xrefing the vtable pointer.

A bit down on the first of 2 xrefed functions, we find:


The fact that that is a11 is great, because assuming this function is a constructor (and it sure looks like it), that'd be one of the params.
There's only 1 xref to this function:


v7+72, feeling it rn. v7 is a1 for that function, so let's bp it and see.
So it only seems to get called when you create another window. (What we need is something that is only called when sublime is started and maybe sporadically)

So now there's 2 things:


and a 524498 vtable



window_list, cool.
So window_list + 72 is seemingly the license info.
I'm gonna go for that pointer first. sublime_text.exe+6A7AD0

So, V7+72 always seems to have the same value, which is 0xC8. Now that doesn't really add up with the rest of the code we've been following and the values, but at least we know it's got something to do with the License.

Imma start it manually with x64dbg and hopefully bp it.
Results:
00007FF741A62CD8 (RVA:22CD8) | 4C 89 05 F1 4D 68 00 | mov qword ptr ds:[7FF7420E7AD0(RVA:6A7AD0)],r8 |

Great! IDA time.


Ok, so this seems to be what sets the pointers. Gladly there's only one xref.

And we're now seeing some cool shit!




This means we're close. But I doubt it's gonna be easy.

Anywho, we know from the prev function, the window_list is the third arg (r8), so let's see where it's set.
It would seem the binary does some weird stuff that confuses the decompiler, so this is gonna have to be asm only.

Here's the pointer to the previous function:


And it's only used again towards the end of the function (in what I find is a weird fashion):

R8 is set before, depending on a condition, so chances are this is the 1st stack variable.

Anyhow, we'll need to bp this call (RVA: 3A744), otherwise there's no way to know what that function is, since the only xref to the current function is in rdata.
Feelsbad. Seems like it's never called!?
I mean the function itself is, so at least we can go back to the caller to see if there's something interesting.

~Fucking exceptions~

Okay, so I BPd the beginning and the end of the function, in the beginning there's no window, by the end there already is (with the unregistered window name and everything).
The call is made at sublime_text.exe+D4ADA.
Good shit.

So this is gonna be complicated, at this point it just looks like a cesspool of indirect calls and no xrefs.


Honestly I'm getting lost. I need to know where that v7+72 comes from and I'm straying from the path.



The Big Gay

So I was confused and I went back.
Noticed my code was wrong.
it is not
License_Info__ = 0;
if(!Something_License_->isRegistered)
License_Info__ = 2
if(!Something_License_->UpgradeRequired)
License_Info__ |= 4;

but indeed
License_Info__ = 0;
if(!*(char*)(Something_License_->isRegistered))
License_Info__ = 2
if(!*(char*)(Something_License_->UpgradeRequired))
License_Info__ |= 4;


Which means I've been chasing dumb shit. And guess what the best part is:



It's a fucking global pointer.

Care to see what happens when you change it to 1?



fml.

Anyway, that's cracked, it's initialized (compile-time) to 0, so all it really takes to crack subl is change that to 1.


The Juicy Info

Offset of the variable for the current version (march 2018) is sublime_text.exe + 684920
File offset is 682320.

I checked what accessed it, and found that it's accessed both when you change tabs (the title) and when you save (the message).

There's a problem though, if you change that byte to 1 in the file, it'll start as registered, but then after like 5 minutes go back to unregistered. I tried BPing it, but they're using some kind of debugger trap shenanigans, and honestly I don't have the patience right now, plus there are only 2 instructions that use it so might as well just patch those.

The 3 pieces of code that use the variable directly(for anyone wanting to make a sig):
.text:0000000140038F4C 48 8D 05 CD B9 64 00                                lea     rax, IsRegistered
.text:0000000140038F53 48 89 86 38 02 00 00 mov [rsi+238h], rax
.text:0000000140038F5A 33 ED xor ebp, ebp
.text:0000000140038F5C 48 89 AE 40 02 00 00 mov [rsi+240h], rbp
.text:0000000140038F63 48 89 AE 48 02 00 00 mov [rsi+248h], rbp
.text:0000000140038F6A 89 AE 50 02 00 00 mov [rsi+250h], ebp
.text:0000000140038F70 41 83 CE FF or r14d, 0FFFFFFFFh
.text:0000000140038F74 66 44 89 B6 54 02 00 00 mov [rsi+254h], r14w
.text:0000000140038F7C 66 44 89 B6 56 02 00 00 mov [rsi+256h], r14w
.text:0000000140038F84 89 AE 78 02 00 00 mov [rsi+278h], ebp
.text:0000000140038F8A 66 44 89 B6 7C 02 00 00 mov [rsi+27Ch], r14w
.text:0000000140038F92 66 44 89 B6 7E 02 00 00 mov [rsi+27Eh], r14w
.text:0000000140038F9A 48 89 AE A0 02 00 00 mov [rsi+2A0h], rbp
.text:0000000140038FA1 48 89 AE A8 02 00 00 mov [rsi+2A8h], rbp
.text:0000000140038FA8 48 89 AE B0 02 00 00 mov [rsi+2B0h], rbp
.text:0000000140038FAF 48 89 AE B8 02 00 00 mov [rsi+2B8h], rbp
.text:0000000140038FB6 48 89 AE C0 02 00 00 mov [rsi+2C0h], rbp
.text:0000000140038FBD 89 AE C8 02 00 00 mov [rsi+2C8h], ebp
.text:0000000140038FC3 48 89 AE D0 02 00 00 mov [rsi+2D0h], rbp
.text:0000000140038FCA 48 89 AE D8 02 00 00 mov [rsi+2D8h], rbp
.text:0000000140038FD1 48 89 AE E0 02 00 00 mov [rsi+2E0h], rbp
.text:0000000140038FD8 B8 00 10 00 00 mov eax, 1000h
.text:0000000140038FDD 48 89 86 E8 02 00 00 mov [rsi+2E8h], rax
.text:0000000140038FE4 48 89 86 F0 02 00 00 mov [rsi+2F0h], rax
.text:0000000140038FEB 48 89 AE F8 02 00 00 mov [rsi+2F8h], rbp
.text:0000000140038FF2 48 89 AE 00 03 00 00 mov [rsi+300h], rbp
.text:0000000140038FF9 48 89 AE 08 03 00 00 mov [rsi+308h], rbp
.text:0000000140039000 48 89 AE 10 03 00 00 mov [rsi+310h], rbp
.text:0000000140039007 48 89 AE 18 03 00 00 mov [rsi+318h], rbp
.text:000000014003900E 89 AE 20 03 00 00 mov [rsi+320h], ebp
.text:0000000140039014 48 89 AE 28 03 00 00 mov [rsi+328h], rbp
.text:000000014003901B 48 89 AE 30 03 00 00 mov [rsi+330h], rbp
.text:0000000140039022 48 89 AE 38 03 00 00 mov [rsi+338h], rbp
.text:0000000140039029 48 89 86 40 03 00 00 mov [rsi+340h], rax
.text:0000000140039030 48 89 86 48 03 00 00 mov [rsi+348h], rax
.text:0000000140039037 48 89 AE 50 03 00 00 mov [rsi+350h], rbp
.text:000000014003903E 48 89 AE 58 03 00 00 mov [rsi+358h], rbp
.text:0000000140039045 48 89 AE 68 03 00 00 mov [rsi+368h], rbp
.text:000000014003904C 48 89 AE 70 03 00 00 mov [rsi+370h], rbp
.text:0000000140039053 48 89 AE 78 03 00 00 mov [rsi+378h], rbp
.text:000000014003905A 48 89 9E 88 03 00 00 mov [rsi+388h], rbx
.text:0000000140039061 48 89 BE 90 03 00 00 mov [rsi+390h], rdi
.text:0000000140039068 48 8B 44 24 70 mov rax, [rsp+48h+arg_20]
.text:000000014003906D 48 89 86 98 03 00 00 mov [rsi+398h], rax


.text:0000000140079736 48 8D 0D E3 B1 60 00                                lea     rcx, IsRegistered
.text:000000014007973D E8 5A E1 FF FF call sub_14007789C
.text:0000000140079742 48 89 75 88 mov [rbp-78h], rsi
.text:0000000140079746 48 8D 0D 3F FB FF FF lea rcx, sub_14007928C
.text:000000014007974D 48 85 C9 test rcx, rcx
.text:0000000140079750 74 1A jz short loc_14007976C
.text:0000000140079752 48 8D 05 47 C2 4A 00 lea rax, off_1405259A0
.text:0000000140079759 48 89 44 24 50 mov [rsp+488h+var_438], rax
.text:000000014007975E 48 89 4C 24 58 mov [rsp+488h+var_430], rcx
.text:0000000140079763 48 8D 44 24 50 lea rax, [rsp+488h+var_438]
.text:0000000140079768 48 89 45 88 mov [rbp-78h], rax
.text:000000014007976C
.text:000000014007976C loc_14007976C: ; CODE XREF: sub_14007929C+4B4j
.text:000000014007976C 45 33 C0 xor r8d, r8d
.text:000000014007976F 48 8D 54 24 50 lea rdx, [rsp+488h+var_438]
.text:0000000140079774 48 8D 0D E5 08 63 00 lea rcx, qword_1406AA060
.text:000000014007977B E8 B4 3D 1D 00 call sub_14024D534
.text:0000000140079780 90 nop
.text:0000000140079781 48 8B 4D 88 mov rcx, [rbp-78h]
.text:0000000140079785 48 85 C9 test rcx, rcx
.text:0000000140079788 74 12 jz short loc_14007979C
.text:000000014007978A 48 8B 01 mov rax, [rcx]
.text:000000014007978D 48 8D 54 24 50 lea rdx, [rsp+488h+var_438]
.text:0000000140079792 48 3B CA cmp rcx, rdx
.text:0000000140079795 0F 95 C2 setnz dl
.text:0000000140079798 FF 50 20 call qword ptr [rax+20h]
.text:000000014007979B 90 nop
.text:000000014007979C
.text:000000014007979C loc_14007979C: ; CODE XREF: sub_14007929C+498j
.text:000000014007979C ; sub_14007929C+4ECj
.text:000000014007979C 48 8B 54 24 48 mov rdx, [rsp+488h+var_440]
.text:00000001400797A1 48 83 FA 10 cmp rdx, 10h
.text:00000001400797A5 72 11 jb short loc_1400797B8
.text:00000001400797A7 48 FF C2 inc rdx
.text:00000001400797AA 4D 8B C7 mov r8, r15
.text:00000001400797AD 48 8B 4C 24 30 mov rcx, qword ptr [rsp+488h+var_458]
.text:00000001400797B2 E8 39 B5 F8 FF call sub_140004CF0
.text:00000001400797B7 90 nop
.text:00000001400797B8
.text:00000001400797B8 loc_1400797B8: ; CODE XREF: sub_14007929C+41Fj
.text:00000001400797B8 ; sub_14007929C+509j
.text:00000001400797B8 48 8D 4D 00 lea rcx, [rbp+0]
.text:00000001400797BC E8 9F 46 14 00 call sub_1401BDE60
.text:00000001400797C1 90 nop


;unfortunately this is the whole function
.text:00000001404AFF74 48 8D 0D A5 49 1D 00 lea rcx, IsRegistered
.text:00000001404AFF7B E9 EC 74 BC FF jmp sub_14007746C
.text:00000001404AFF80
.text:00000001404AFF80 ; =============== S U B R O U T I N E =======================================
.text:00000001404AFF80
.text:00000001404AFF80
.text:00000001404AFF80 sub_1404AFF80 proc near ; DATA XREF: sub_140082F30+6Eo
.text:00000001404AFF80 ; .pdata:00000001406E450Co
.text:00000001404AFF80
.text:00000001404AFF80 var_18 = qword ptr -18h
.text:00000001404AFF80 arg_8 = qword ptr 10h
.text:00000001404AFF80 arg_10 = qword ptr 18h
.text:00000001404AFF80
.text:00000001404AFF80 40 57 push rdi
.text:00000001404AFF82 48 83 EC 30 sub rsp, 30h
.text:00000001404AFF86 48 C7 44 24 20 FE FF FF FF mov [rsp+38h+var_18], 0FFFFFFFFFFFFFFFEh
.text:00000001404AFF8F 48 89 5C 24 48 mov [rsp+38h+arg_8], rbx
.text:00000001404AFF94 48 89 74 24 50 mov [rsp+38h+arg_10], rsi
.text:00000001404AFF99 48 8B 3D A0 1E 20 00 mov rdi, cs:qword_1406B1E40
.text:00000001404AFFA0 48 8B 1F mov rbx, [rdi]
.text:00000001404AFFA3 33 F6 xor esi, esi
.text:00000001404AFFA5 48 3B DF cmp rbx, rdi
.text:00000001404AFFA8 74 55 jz short loc_1404AFFFF
.text:00000001404AFFAA
.text:00000001404AFFAA loc_1404AFFAA: ; CODE XREF: sub_1404AFF80+76j
.text:00000001404AFFAA 8B 53 40 mov edx, [rbx+40h]
.text:00000001404AFFAD 48 8B 0D 9C 7F 1F 00 mov rcx, cs:qword_1406A7F50
.text:00000001404AFFB4 E8 FF 52 D9 FF call sub_1402452B8
.text:00000001404AFFB9 40 38 73 19 cmp [rbx+19h], sil
.text:00000001404AFFBD 75 34 jnz short loc_1404AFFF3
.text:00000001404AFFBF 48 8B 43 10 mov rax, [rbx+10h]
.text:00000001404AFFC3 40 38 70 19 cmp [rax+19h], sil
.text:00000001404AFFC7 75 0E jnz short loc_1404AFFD7
.text:00000001404AFFC9
.text:00000001404AFFC9 loc_1404AFFC9: ; CODE XREF: sub_1404AFF80+53j
.text:00000001404AFFC9 48 8B D8 mov rbx, rax
.text:00000001404AFFCC 48 8B 00 mov rax, [rax]
.text:00000001404AFFCF 40 38 70 19 cmp [rax+19h], sil
.text:00000001404AFFD3 74 F4 jz short loc_1404AFFC9
.text:00000001404AFFD5 EB 1C jmp short loc_1404AFFF3
.text:00000001404AFFD7 ; ---------------------------------------------------------------------------
.text:00000001404AFFD7
.text:00000001404AFFD7 loc_1404AFFD7: ; CODE XREF: sub_1404AFF80+47j
.text:00000001404AFFD7 48 8B 43 08 mov rax, [rbx+8]
.text:00000001404AFFDB EB 0D jmp short loc_1404AFFEA
.text:00000001404AFFDD ; ---------------------------------------------------------------------------
.text:00000001404AFFDD
.text:00000001404AFFDD loc_1404AFFDD: ; CODE XREF: sub_1404AFF80+6Ej
.text:00000001404AFFDD 48 3B 58 10 cmp rbx, [rax+10h]
.text:00000001404AFFE1 75 0D jnz short loc_1404AFFF0
.text:00000001404AFFE3 48 8B D8 mov rbx, rax
.text:00000001404AFFE6 48 8B 40 08 mov rax, [rax+8]

And the two instructions that 1. check it for the window title, and 2. check it for the file save:
File Offset: 5B3BE

.text:000000014005BFBE 8A 0A mov cl, [rdx] //change to mov cl, 01 (B1 01)
.text:000000014005BFC0 F6 D9 neg cl
.text:000000014005BFC2 45 1B C0 sbb r8d, r8d
.text:000000014005BFC5 41 F7 D0 not r8d
.text:000000014005BFC8 41 83 E0 02 and r8d, 2
.text:000000014005BFCC 45 8B F0 mov r14d, r8d
.text:000000014005BFCF 41 83 CE 04 or r14d, 4
.text:000000014005BFD3 38 5A 01 cmp [rdx+1], bl
.text:000000014005BFD6 45 0F 44 F0 cmovz r14d, r8d
.text:000000014005BFDA 41 B4 01 mov r12b, 1
.text:000000014005BFDD 48 8D 05 04 72 4C 00 lea rax, aShow_full_path ; "show_full_path"
.text:000000014005BFE4 48 89 44 24 30 mov qword ptr [rsp+4E8h+var_4B8], rax
.text:000000014005BFE9 48 8D 05 06 72 4C 00 lea rax, aShow_full_path+0Eh ; ""
.text:000000014005BFF0 48 89 44 24 38 mov qword ptr [rsp+4E8h+var_4B8+8], rax
.text:000000014005BFF5 0F 28 44 24 30 movaps xmm0, [rsp+4E8h+var_4B8]
.text:000000014005BFFA 66 0F 7F 44 24 30 movdqa [rsp+4E8h+var_4B8], xmm0
.text:000000014005C000 48 8D 54 24 30 lea rdx, [rsp+4E8h+var_4B8]
.text:000000014005C005 48 8B 8F 08 02 00 00 mov rcx, [rdi+208h]
.text:000000014005C00C E8 5B 6E 16 00 call sub_1401C2E6C
.text:000000014005C011 48 85 C0 test rax, rax
.text:000000014005C014 74 09 jz short loc_14005C01F
.text:000000014005C016 83 38 02 cmp dword ptr [rax], 2
.text:000000014005C019 75 04 jnz short loc_14005C01F
.text:000000014005C01B 44 8A 60 08 mov r12b, [rax+8]
.text:000000014005C01F
.text:000000014005C01F loc_14005C01F: ; CODE XREF: edit_window__SOMETHING1+B0j
.text:000000014005C01F ; edit_window__SOMETHING1+B5j
.text:000000014005C01F 41 8B F6 mov esi, r14d
.text:000000014005C022 83 CE 08 or esi, 8
.text:000000014005C025 45 84 E4 test r12b, r12b
.text:000000014005C028 41 0F 44 F6 cmovz esi, r14d
.text:000000014005C02C 48 89 5D 80 mov [rbp-80h], rbx
.text:000000014005C030 41 BC 00 02 00 00 mov r12d, 200h
.text:000000014005C036 4C 89 65 88 mov [rbp-78h], r12
.text:000000014005C03A 48 8D 45 98 lea rax, [rbp-68h]
.text:000000014005C03E 48 89 45 90 mov [rbp-70h], rax
.text:000000014005C042 4C 8B F3 mov r14, rbx
.text:000000014005C045 48 39 9F 70 01 00 00 cmp [rdi+170h], rbx
.text:000000014005C04C 74 64 jz short loc_14005C0B2
.text:000000014005C04E 48 8D 97 60 01 00 00 lea rdx, [rdi+160h]
.text:000000014005C055 48 8D 4C 24 40 lea rcx, [rsp+4E8h+var_4A8]
.text:000000014005C05A E8 91 25 1F 00 call sub_14024E5F0
.text:000000014005C05F 0F 10 00 movups xmm0, xmmword ptr [rax]
.text:000000014005C062 F3 0F 7F 44 24 30 movdqu [rsp+4E8h+var_4B8], xmm0
.text:000000014005C068 48 8D 54 24 30 lea rdx, [rsp+4E8h+var_4B8]
.text:000000014005C06D 48 8D 4C 24 60 lea rcx, [rsp+4E8h+var_488]
.text:000000014005C072 E8 C1 F8 1E 00 call sub_14024B938


File Offset: 77D91

.text:0000000140078991 75 76 jnz short loc_140078A09 //change to jmp(EB 76)
.text:0000000140078993 E8 08 C4 1D 00 call sub_140254DA0
.text:0000000140078998 48 8B C8 mov rcx, rax
.text:000000014007899B 48 8B D8 mov rbx, rax
.text:000000014007899E 48 2B 0D 53 8D 63 00 sub rcx, cs:qword_1406B16F8
.text:00000001400789A5 48 81 F9 80 CB A4 00 cmp rcx, 0A4CB80h
.text:00000001400789AC 7C 5B jl short loc_140078A09
.text:00000001400789AE FF 05 40 8D 63 00 inc cs:dword_1406B16F4
.text:00000001400789B4 E8 27 21 3E 00 call sub_14045AAE0
.text:00000001400789B9 25 0F 00 00 80 and eax, 8000000Fh
.text:00000001400789BE 7D 07 jge short loc_1400789C7
.text:00000001400789C0 FF C8 dec eax
.text:00000001400789C2 83 C8 F0 or eax, 0FFFFFFF0h
.text:00000001400789C5 FF C0 inc eax
.text:00000001400789C7
.text:00000001400789C7 loc_1400789C7: ; CODE XREF: sub_140078988+36j
.text:00000001400789C7 8B 0D 27 8D 63 00 mov ecx, cs:dword_1406B16F4
.text:00000001400789CD 85 C0 test eax, eax
.text:00000001400789CF 75 05 jnz short loc_1400789D6
.text:00000001400789D1 83 F9 02 cmp ecx, 2
.text:00000001400789D4 7F 05 jg short loc_1400789DB
.text:00000001400789D6
.text:00000001400789D6 loc_1400789D6: ; CODE XREF: sub_140078988+47j
.text:00000001400789D6 83 F9 08 cmp ecx, 8
.text:00000001400789D9 7E 2E jle short loc_140078A09
.text:00000001400789DB
.text:00000001400789DB loc_1400789DB: ; CODE XREF: sub_140078988+4Cj
.text:00000001400789DB 48 8B 15 3E D0 4A 00 mov rdx, cs:off_140525A20
.text:00000001400789E2 48 8D 0D 77 16 63 00 lea rcx, qword_1406AA060
.text:00000001400789E9 83 25 04 8D 63 00 00 and cs:dword_1406B16F4, 0
.text:00000001400789F0 48 81 C2 DA 5E 2C D9 add rdx, 0FFFFFFFFD92C5EDAh
.text:00000001400789F7 45 33 C9 xor r9d, r9d
.text:00000001400789FA 48 89 1D F7 8C 63 00 mov cs:qword_1406B16F8, rbx
.text:0000000140078A01 45 33 C0 xor r8d, r8d
.text:0000000140078A04 E8 DB 4B 1D 00 call sub_14024D5E4
.text:0000000140078A09
.text:0000000140078A09 loc_140078A09: ; CODE XREF: sub_140078988+9j
.text:0000000140078A09 ; sub_140078988+24j ...
.text:0000000140078A09 48 83 C4 20 add rsp, 20h
.text:0000000140078A0D 5B pop rbx
.text:0000000140078A0E C3 retn
.text:0000000140078A0E sub_140078988 endp



Le End

I still didn't own the whole registration system as I was planning initially, but rn I don't have time, and it works as is, might revisit in the future in another thread.
cyaz