Crackme Exploration
According to the description, this is a KeygenMe challenge written in pure x86 Assembly.
Upon launching the executable file, I’m quite impressed with how the author could create such a cool user interface like this with just Assembly! I was just expecting a TUI-based challenge, but seeing this GUI just feels like a great start.
Entering some random strings into the Name and Serial text boxes yielded an error message box, indicating an incorrect serial key.
In previous write-ups, I’ve never used the Detect It Easy tool for analyzing an executable for a challenge prior to solving. Since the crackme’s description hinted that the executable is packed, let’s open it up in Detect It Easy to see some preliminary information.
And yes, the crackme is packed using UPX.
Also, I discovered that we can actually press the YARA button and use YARA rules to find additional information that can help us with solving the challenge.
From the YARA rules, seems like there are some cryptography things inside the crackme. We’re not sure about their actual usage, but they might be helpful later.
Unpacking The Crackme
I’ve never dealt with a UPX-packed executable before, so I went to Google and came across this article explaining how to unpack one:
I’m going to follow this article just for unpacking this crackme, then I’ll definitely look into this in more detail later!
Finding The OEP
In Binary Ninja, I quickly set a breakpoint at the jmp right after the popad instruction at the end of _start():
I then stepped into the jmp and it took me to sub_401000.
Just by looking at some strings, this function seems to be the program’s main logic. Here, the jmp took me to the OEP (Original Entry Point) of the binary, so we get 00401000 as the OEP of the crackme!
Let’s extract the unpacked executable from this OEP.
Dumping The Unpacked PE
To do this, I used a tool called Scylla. I’m going to run the crackme first, and choose the crackme process in the Attach to an active process dropdown.
And… our OEP isn’t correct at all. Let’s fix that to 00401000. Then click on IAT Autosearch to scan for imports.
When this dialog pops up, click on Yes.
And press on Get Imports once Scylla finishes.
Finally, click on Dump to extract the unpacked binary. Then click on Fix Dump and choose the recently dumped binary to fix its IAT.
The executable ending with _SCY.exe is our unpacked binary!
Now we can open the unpacked executable in a disassembler!
Cracking Time
Let’s open the unpacked binary in Binary Ninja. In the _start function, there are quite some debugger checks and probably some anti-patching there.
Scrolling a bit down to the end of the _start() function… But wait, where is the serial key validation logic?
I opened the Strings view and look for the strings for the message boxes that we saw earlier. So somehow this part isn’t disassembled yet.
Then I decided to do a pretty dumb thing. I NOP-ed all the code of some debugger checks, starting from 00401035 to 0040113c. I’m not sure what really happened but Binary Ninja just decided to disassemble the main logic of the program!
To be honest I was quite tempted to just decompile this section into pseudocode because I was too lazy to read raw Assembly. But I decided to stay true to the author’s intent and analyze the entire logic directly through the disassembly.
Serial Construction Logic
Since the Name and Serial are entered through EditBoxes, we’ll focus on the GetDlgItemTextA calls. These appear at addresses 00401211, 00401280, and 00401308, which correspond to the parts of the code that read input from the text boxes, generate the key from the name, and compare it to the entered serial.
004011ff 6800020000 push 0x200
00401204 681de14000 push 0x40e11d
00401209 68f3030000 push 0x3f3
0040120e ff7508 push dword [ebp+0x8 {arg1}]
00401211 e840080000 call GetDlgItemTextA
00401216 681de14000 push 0x40e11d
0040121b e8de080000 call lstrlenA
00401220 a31ded4000 mov dword [data_40ed1d], eax
00401225 69c039050000 imul eax, eax, 0x539
0040122b 50 push eax
0040122c 6809c94000 push 0x40c909
00401231 681de54000 push 0x40e51d
00401236 e8f1070000 call wsprintfA
0040123b 681de34000 push 0x40e31d
00401240 ff351ded4000 push dword [data_40ed1d]
00401246 681de14000 push 0x40e11d
0040124b e890800000 call sub_4092e0
00401250 681de34000 push 0x40e31d
00401255 681de74000 push 0x40e71d
0040125a e899080000 call lstrcpyA
0040125f 681de54000 push 0x40e51d
00401264 681de74000 push 0x40e71d
00401269 e87e080000 call lstrcatA
0040126e 6800020000 push 0x200
00401273 6821f14000 push 0x40f121
00401278 680dc94000 push 0x40c90d {"KPCR tapz"}
0040127d ff7508 push dword [ebp+0x8 {arg1}]
00401280 e8d1070000 call GetDlgItemTextA
00401285 50 push eax
00401286 6821f14000 push 0x40f121
0040128b e880620000 call sub_407510
00401290 6821ed4000 push 0x40ed21
00401295 681de74000 push 0x40e71d
0040129a e849630000 call sub_4075e8
0040129f 6821ef4000 push 0x40ef21
004012a4 ff351ded4000 push dword [data_40ed1d]
004012aa 6821ed4000 push 0x40ed21
004012af e82c800000 call sub_4092e0
004012b4 8bf8 mov edi, eax
004012b6 e8b97e0000 call sub_409174
004012bb 57 push edi
004012bc 6821ef4000 push 0x40ef21
004012c1 e80a7f0000 call sub_4091d0
004012c6 e8797f0000 call sub_409244
004012cb 681deb4000 push 0x40eb1d
004012d0 6a20 push 0x20
004012d2 50 push eax {data_4176c8}
004012d3 e808800000 call sub_4092e0
004012d8 681deb4000 push 0x40eb1d
004012dd 681de74000 push 0x40e71d
004012e2 e811080000 call lstrcpyA
004012e7 6817c94000 push 0x40c917 {"-OMGWTFBBQ"}
004012ec 681de74000 push 0x40e71d
004012f1 e8f6070000 call lstrcatA
004012f6 6800020000 push 0x200
004012fb 6829f34000 push 0x40f329
00401300 68f7030000 push 0x3f7
00401305 ff7508 push dword [ebp+0x8 {arg1}]
00401308 e849070000 call GetDlgItemTextA
0040130d 681de74000 push 0x40e71d
00401312 6829f34000 push 0x40f329
00401317 e8d6070000 call lstrcmpiA
0040131c 0bc0 or eax, eax
0040131e 7416 je 0x401336
00401320 6a10 push 0x10
00401322 6894cf4000 push 0x40cf94 {"BarbecueMe"}
00401327 6860cf4000 push 0x40cf60 {"OMG Sausages burning\r\nYour serial is not correct"}
0040132c ff7508 push dword [ebp+0x8 {arg1}]
0040132f e83a070000 call MessageBoxA
00401334 eb5f jmp 0x401395
00401336 6a40 push 0x40
00401338 68c4cf4000 push 0x40cfc4 {"BarbecueMe"}
0040133d 68a0cf4000 push 0x40cfa0 {"w00t, w00t\r\nYour serial is correct"}
00401342 ff7508 push dword [ebp+0x8 {arg1}]
00401345 e824070000 call MessageBoxA
0040134a c9 leave {__saved_ebp}
0040134b c21000 retn 0x10 {__return_addr}Let’s start analyzing it.
Hex-ifying Variables
First, the program retrieves the value for the Name field through GetDlgItemTextA and save it to a variable at the address 0x40e11d. According to the documentation for GetDlgItemTextA at MicrosoftLearn jwmsft GetDlgItemTextA function (winuser.h) - Win32 apps, the third argument is the buffer to receive the text, and that corresponds to the variable at
0x40e11d.
004011ff 6800020000 push 0x200
00401204 681de14000 push 0x40e11d
00401209 68f3030000 push 0x3f3
0040120e ff7508 push dword [ebp+0x8]
00401211 e840080000 call GetDlgItemTextAThen the length of the Name text is calculated at 0x40e11d and saved to eax, then eax is multiplied by 0x539.
00401216 681de14000 push 0x40e11d
0040121b e8de080000 call lstrlenA
00401220 a31ded4000 mov dword [data_40ed1d], eax
00401225 69c039050000 imul eax, eax, 0x539The value of eax is now converted to a hexadecimal string using wsprintfA and saved into a variable at 0x40e51d.
The wsprintfA function takes three argument, a buffer to store the output, a format string that defines how to format the text, and a variable list of values to insert into that format. Here, the variable at 0x40c909 holds the format string and has a value of %.X. From the Remarks section at MicrosoftLearn jwmsft wsprintfA function (winuser.h) - Win32 apps, the resulting string of this
wsprintfA call will be an uppercase hexadecimal string.
0040122b 50 push eax
0040122c 6809c94000 push 0x40c909
00401231 681de54000 push 0x40e51d
00401236 e8f1070000 call wsprintfAThere’s this sub_4092e0 function which takes the Name string at 0x40e11d, Name string length at 0x40e11d, and a variable at 0x40e31d.
0040123b 681de34000 push 0x40e31d
00401240 ff351ded4000 push dword [data_40ed1d]
00401246 681de14000 push 0x40e11d
0040124b e890800000 call sub_4092e0This function is actually for converting raw bytes to an uppercase hex string, and write the result to a variable at 0x40e31d.
004092e0 void* __fastcall sub_4092e0(int32_t arg1, int32_t arg2, char* arg3, int32_t arg4, void* arg5)
004092e0 55 push ebp {__saved_ebp}
004092e1 8bec mov ebp, esp {__saved_ebp}
004092e3 57 push edi {__saved_edi}
004092e4 56 push esi {__saved_esi}
004092e5 53 push ebx {__saved_ebx}
004092e6 8b5d0c mov ebx, dword [ebp+0xc {arg4}]
004092e9 8b7d10 mov edi, dword [ebp+0x10 {arg5}]
004092ec 85db test ebx, ebx
004092ee 8b7508 mov esi, dword [ebp+0x8 {arg3}]
004092f1 7436 je 0x409329
004092f3 0fb606 movzx eax, byte [esi]
004092f6 8bc8 mov ecx, eax
004092f8 83c702 add edi, 0x2
004092fb c1e904 shr ecx, 0x4
004092fe 83e00f and eax, 0xf
00409301 83e10f and ecx, 0xf
00409304 83f80a cmp eax, 0xa
00409307 1bd2 sbb edx, edx
00409309 83d000 adc eax, 0x0
0040930c 8d44d037 lea eax, [eax+edx*8+0x37]
00409310 83f90a cmp ecx, 0xa
00409313 1bd2 sbb edx, edx
00409315 83d100 adc ecx, 0x0
00409318 c1e008 shl eax, 0x8
0040931b 8d4cd137 lea ecx, [ecx+edx*8+0x37]
0040931f 0bc1 or eax, ecx
00409321 46 inc esi
00409322 668947fe mov word [edi-0x2], ax
00409326 4b dec ebx
00409327 75ca jne 0x4092f3
00409329 8bc7 mov eax, edi
0040932b c60700 mov byte [edi], 0x0
0040932e 2b4510 sub eax, dword [ebp+0x10 {arg5}]
00409331 5b pop ebx {__saved_ebx}
00409332 5e pop esi {__saved_esi}
00409333 5f pop edi {__saved_edi}
00409334 c9 leave {__saved_ebp}
00409335 c20c00 retn 0xc {__return_addr}We then encountered some calls to lstrcpyA and lstrcatA. These are just copying the hexadecimal Name string to 0x40e71d, and concatenate it with the hexadecimal Name string length at 0x40e51d.
00401250 681de34000 push 0x40e31d
00401255 681de74000 push 0x40e71d
0040125a e899080000 call lstrcpyA
0040125f 681de54000 push 0x40e51d
00401264 681de74000 push 0x40e71d
00401269 e87e080000 call lstrcatALet’s sum all of this up a little bit. Here’s what we’ve got:
- The Name EditBox value is retrieved and saved into
0x40e11d. - The string length of the Name value is calculated, multiplied by
0x539, and converted into an uppercase hexadecimal string. We’ll refer to this ashex_name_length. - The Name value is converted into a hexadecimal string as well, and then gets concatenated with
hex_name_length. We’ll call the concatenated stringhex_name_and_length.
An Encryption Algorithm?
I really don’t know what the second GetDlgItemTextA is doing here though. It stores the retrieved value into a variable at 0x40F121, which is then passed to sub_407510, but function doesn’t appear to do anything related to our serial construction. So I’ll just skip this.
00407510 void __stdcall sub_407510(int32_t arg1, int32_t arg2)
00407510 55 push ebp {__saved_ebp}
00407511 8bec mov ebp, esp {__saved_ebp}
00407513 56 push esi {__saved_esi}
00407514 57 push edi {__saved_edi}
00407515 53 push ebx {__saved_ebx}
00407516 bf78664100 mov edi, data_416678
0040751b bef8d04000 mov esi, 0x40d0f8
00407520 b900040000 mov ecx, 0x400
00407525 f3a5 rep movsd dword [edi], [esi] {0x0}
00407527 8b5d0c mov ebx, dword [ebp+0xc {arg2}]
0040752a 8b7508 mov esi, dword [ebp+0x8 {arg1}]
0040752d 83fb04 cmp ebx, 0x4
00407530 0f82a9000000 jb 0x4075df
00407536 83fb38 cmp ebx, 0x38
00407539 0f87a0000000 ja 0x4075df
0040753f 33c9 xor ecx, ecx {0x0}
00407541 33ff xor edi, edi {0x0}
00407543 33d2 xor edx, edx {0x0}
00407545 33c0 xor eax, eax {0x0}
00407547 c1e008 shl eax, 0x8
0040754a 0a0437 or al, byte [edi+esi]
0040754d 47 inc edi
0040754e 3bfb cmp edi, ebx
00407550 7202 jb 0x407554
00407552 33ff xor edi, edi {0x0}
00407554 42 inc edx
00407555 83fa04 cmp edx, 0x4
00407558 75ed jne 0x407547
0040755a 33048db0d04000 xor eax, dword [ecx*4+0x40d0b0]
00407561 89048d30664100 mov dword [ecx*4+0x416630], eax
00407568 41 inc ecx
00407569 83f912 cmp ecx, 0x12
0040756c 75d5 jne 0x407543
0040756e 33db xor ebx, ebx {0x0}
00407570 891d78764100 mov dword [data_417678], ebx {0x0}
00407576 891d7c764100 mov dword [data_41767c], ebx {0x0}
0040757c 6878764100 push data_417678
00407581 6878764100 push data_417678
00407586 e85d000000 call sub_4075e8
0040758b 0fc8 bswap eax
0040758d 0fca bswap edx
0040758f 8904dd30664100 mov dword [ebx*8+0x416630], eax
00407596 8914dd34664100 mov dword [ebx*8+0x416634], edx
0040759d 43 inc ebx
0040759e 83fb09 cmp ebx, 0x9
004075a1 75d9 jne 0x40757c {"hxvA"}
004075a3 33ff xor edi, edi {0x0}
004075a5 33db xor ebx, ebx {0x0}
004075a7 6878764100 push data_417678
004075ac 6878764100 push data_417678
004075b1 e832000000 call sub_4075e8
004075b6 0fc8 bswap eax
004075b8 0fca bswap edx
004075ba 8984df78664100 mov dword [edi+ebx*8+0x416678], eax
004075c1 8994df7c664100 mov dword [edi+ebx*8+0x41667c], edx
004075c8 43 inc ebx
004075c9 81fb80000000 cmp ebx, 0x80
004075cf 75d6 jne 0x4075a7 {"hxvA"}
004075d1 81c700040000 add edi, 0x400
004075d7 81ff00100000 cmp edi, 0x1000
004075dd 75c6 jne 0x4075a5
004075df 5b pop ebx {__saved_ebx}
004075e0 5f pop edi {__saved_edi}
004075e1 5e pop esi {__saved_esi}
004075e2 c9 leave {__saved_ebp}
004075e3 c20800 retn 0x8 {__return_addr}Next, the hex_name_and_length string is passed to sub_4075e8 as arg1. At first glance I couldn’t figure out what this function is doing, so I look it up and it turns out that this is actually the Blowfish algorithm!
004075e8 int32_t __stdcall sub_4075e8(int32_t* arg1, int32_t* arg2)
004075e8 55 push ebp {__saved_ebp}
004075e9 8bec mov ebp, esp {__saved_ebp}
004075eb 56 push esi {__saved_esi}
004075ec 57 push edi {__saved_edi}
004075ed 53 push ebx {__saved_ebx}
004075ee 8b7508 mov esi, dword [ebp+0x8 {arg1}]
004075f1 33c9 xor ecx, ecx {0x0}
004075f3 8b06 mov eax, dword [esi]
004075f5 8b5604 mov edx, dword [esi+0x4]
004075f8 0fc8 bswap eax
004075fa 0fca bswap edx
004075fc 33048d30664100 xor eax, dword [ecx*4+0x416630]
00407603 8bf8 mov edi, eax
00407605 8bf0 mov esi, eax
00407607 c1ef18 shr edi, 0x18
0040760a c1ee10 shr esi, 0x10
0040760d 81e6ff000000 and esi, 0xff
00407613 8b1cbd78664100 mov ebx, dword [edi*4+0x416678]
0040761a 031cb5786a4100 add ebx, dword [esi*4+0x416a78]
00407621 0fb6fc movzx edi, ah
00407624 0fb6f0 movzx esi, al
00407627 331cbd786e4100 xor ebx, dword [edi*4+0x416e78]
0040762e 031cb578724100 add ebx, dword [esi*4+0x417278]
00407635 33da xor ebx, edx
00407637 41 inc ecx
00407638 8bd0 mov edx, eax
0040763a 83f910 cmp ecx, 0x10
0040763d 8bc3 mov eax, ebx
0040763f 75bb jne 0x4075fc
00407641 92 xchg edx, eax
00407642 331570664100 xor edx, dword [data_416670]
00407648 330574664100 xor eax, dword [data_416674]
0040764e 8b750c mov esi, dword [ebp+0xc {arg2}]
00407651 0fc8 bswap eax
00407653 0fca bswap edx
00407655 8906 mov dword [esi], eax
00407657 895604 mov dword [esi+0x4], edx
0040765a 5b pop ebx {__saved_ebx}
0040765b 5f pop edi {__saved_edi}
0040765c 5e pop esi {__saved_esi}
0040765d c9 leave {__saved_ebp}
0040765e c20800 retn 0x8 {__return_addr}I don’t have any prior cryptography knowledge, but I’ll try to understand the algorithm.
In this first part, the function initialized a counter variable ecx. The function takes 8 bytes of input (arg1), or hex_name_and_length in this case, and splits this input into two halves of 4 bytes and store them into eax and edx.
push ebp {__saved_ebp}
mov ebp, esp {__saved_ebp}
push esi {__saved_esi}
push edi {__saved_edi}
push ebx {__saved_ebx}
mov esi, dword [ebp+0x8 {arg1}]
xor ecx, ecx ; ecx = 0, used as a loop counter
mov eax, [esi] ; load first 4 bytes of input into eax
mov edx, [esi+4] ; load next 4 bytes of input into edx
bswap eax ; reverse byte order of eax
bswap edx ; reverse byte order of edxHere comes the loop of 16 iterations.
xor eax, [ecx*4+0x416630] ; XOR eax with a 4-byte (32-bit) value
mov edi, eax ; copy eax into edi
mov esi, eax ; copy eax into esi again
shr edi, 24 ; extract the highest byte of eax into edi
shr esi, 16 ; extract the next-highest byte of eax into esi
and esi, 0xff ; keep only the lower 8 bits of esi
mov ebx, [edi*4+0x416678] ; look up a 4-byte value using that high byte
add ebx, [esi*4+0x416A78] ; add another value based on the next byte
movzx edi, ah ; extract the third byte of eax
movzx esi, al ; extract the lowest byte of eax
xor ebx, [edi*4+0x416E78] ; XOR ebx with another value
add ebx, [esi*4+0x417278] ; add yet another value
xor ebx, edx ; XOR ebx with edx
inc ecx ; increase loop counter
mov edx, eax
cmp ecx, 0x10 ; check if 16 iterations are done
mov eax, ebx
jne 0x4075fc ; if not done, repeat the loopAfter the loop finishes, the function do another swap and a couple of XOR operations with some constants at 0x416670 and 0x416674.
xchg edx, eax ; swap eax and edx
xor edx, [0x416670] ; XOR edx with another constant
xor eax, [0x416674] ; XOR eax with another constantFinally, the results are written into the output buffer at arg2.
mov esi, [ebp+0xC] ; esi = arg2 (pointer to output)
bswap eax ; reverse byte order of eax
bswap edx ; reverse byte order of edx
mov [esi], eax ; write first 4 bytes to output
mov [esi+4], edx ; write next 4 bytes to output
pop ebx
pop edi
pop esi
leave
retn 8If you look at how the Blowfish algorithm is implemented in other places, you can see that it uses a P-array consisting of 18 4-byte subkeys (18-entry P-array) and four 4-byte S-boxes with 256 entries (256-entry S-boxes). The values in the P-array and the S-boxes are used for lots of operations inside the 16-iteration loop.
You may notice several addresses inside the loop from the above disassembly, such as [ecx*4+0x416630], [edi*4+0x416678], [esi*4+0x416A78], [edi*4+0x416E78], and [esi*4+0x417278]. Upon comparing the offsets, you'll notice that the difference between the first two is 0x48 (or 72 bytes), whereas the difference between the subsequent offsets is 0x400 (or 1024 bytes). This aligns perfectly with the size of the Blowfish P-array and S-boxes: the P-array is bytes, while each S-box is bytes.
After the loop, there are some addresses as well. 0x416670 and 0x416674 were in fact the last two entries of the P-array (at indices 16 and 17). We know that by doing some simple offset calculations from the base address of the P-array at 0x416630: 0x416630 + (16 * 4) = 0x416670 and 0x416630 + (17 * 4) = 0x416674.
But these arrays only contain zeroes from their initialization!
Wait, the 0x416678 address looks familiar. I’ve seen it somewhere previously. Yes, it’s in the sub_407510 function (that I skipped earlier LOL) where it attempted to copy over the S-boxes at 0x40d0f8 to 0x416678:
00407516 bf78664100 mov edi, data_416678
0040751b bef8d04000 mov esi, 0x40d0f8
00407520 b900040000 mov ecx, 0x400
00407525 f3a5 rep movsd dword [edi], [esi] {0x0}We know this code snippet is copying over 4 S-boxes because REP MOVSD moves 4 bytes (or a dword) per iteration, and mov ecx, 0x400 sets the repeat count to 1024 (0x400) dwords. So it’s basically moving 4096 (0x1000) bytes, which is equivalent to the total size of 4 S-boxes that we’ve seen earlier. If we look at 0x40d0f8, these are the exact values of the S-boxes!
You can compare them with some implementations of Blowfish on the internet:
But what about the P-array? Yes, there is some copying to 0x416630 in sub_407510 as well:
0040755a 33048db0d04000 xor eax, dword [ecx*4+0x40d0b0]
00407561 89048d30664100 mov dword [ecx*4+0x416630], eaxBut then if we look closely at the function, the values in the P-array are set to the hardcoded values at 0x40d0b0 XOR-ed with some bytes only if arg2 ≥ 0x4 && a2 ≤ 0x38.
0040752d 83fb04 cmp ebx, 0x4
00407530 0f82a9000000 jb 0x4075df
00407536 83fb38 cmp ebx, 0x38
00407539 0f87a0000000 ja 0x4075df0x40d0b0 definitely contains the exact 18 entries of the P-array. So if those conditions are met, we would have to deal with a XOR-ed P-array.
Does the values at 0x416630 even set? Tracing back to the _start() function, something is very strange about the second GetDlgItemTextA that we’ve seen earlier:
0040126e 6800020000 push 0x200
00401273 6821f14000 push 0x40f121
00401278 680dc94000 push 0x40c90d {"KPCR tapz"}
0040127d ff7508 push dword [ebp+0x8 {arg1}]
00401280 e8d1070000 call GetDlgItemTextA
00401285 50 push eax
00401286 6821f14000 push 0x40f121
0040128b e880620000 call sub_407510If you look at the second argument of GetDlgItemTextA which corresponds to the ID of the text field, which should be a hardcoded integer, not a memory offset like 0x40c90d. If we treat 0x40c90d as an integer, its value would be over 4 million, far too large for a typical dialog control ID. So this GetDlgItemTextA should fail, no text is retrieved, and the return length is 0. As a result, arg2 of sub_407510 is 0, so the P-array remains all zeroes! So our Blowfish algorithm has no impact from the P-array XORs, and we can easily omit the P-array when we write the keygen!
Let’s get back to the _start() function. The Blowfish-encrypted hex_name_and_length that sub_4075e8 stores at 0x40ed21 is converted into an uppercase hexadecimal string using sub_4092e0 that we’ve analyzed above. The converted string that we’ll be refering as hex_blowfish is then stored in a variable at 0x40ef21.
0040129f 6821ef4000 push 0x40ef21
004012a4 ff351ded4000 push dword [data_40ed1d]
004012aa 6821ed4000 push 0x40ed21
004012af e82c800000 call sub_4092e0
004012b4 8bf8 mov edi, eaxAt this point, I noticed something strange: sub_4092e0 takes two arguments — the encrypted hex_name_and_length value and the length of the Name string (which comes from the dialog’s Name field). The encrypted hex_name_and_length (let’s call it blowfish_output) is always 8 bytes long, but the function is given name_len (the length of the Name string) as if that were the size of the data. What if name_len is greater than 8?
If you take a closer look at sub_4092e0, you'll notice that there’s no bounds checking. The function just reads past the real data starting from 0x40ed21 and convert whatever bytes are in memory there. The variable at 0x40ed21 is initialized with 512 bytes (defined as db 3 dup(0) + dd 7Fh dup(0) + db 0, which totals to 512 bytes: ). As a result, the function appends zero bytes to the end of the hexadecimal string as a form of padding.
Hashing, And Serial Found!
The next 3 functions performs SHA-256 hashing of the hex_blowfish string at 0x40ef21. At this point I’m too lazy to explain the details inside these functions, but anyways here’s the basic idea:
-
sub_409174: Resets everything and sets the SHA-256 starting values. -
sub_4091d0: Adds the input data from thehex_blowfishstring to the hash and processes it in 64-byte chunks. -
sub_409244: Finishes the hash, adds padding, swaps bytes, and returns the 32-byte result.
Then sub_4092e0 is called to turn those 32 bytes into a 64-character hex string at 0x40eb1d.
004012b6 e8b97e0000 call sub_409174
004012bb 57 push edi
004012bc 6821ef4000 push 0x40ef21
004012c1 e80a7f0000 call sub_4091d0
004012c6 e8797f0000 call sub_409244
004012cb 681deb4000 push 0x40eb1d
004012d0 6a20 push 0x20
004012d2 50 push eax {data_4176c8}
004012d3 e808800000 call sub_4092e0And finally, the hex string is appended -OMGWTFBBQ at the end, and that’s the final serial key!
004012d8 681deb4000 push 0x40eb1d
004012dd 681de74000 push 0x40e71d
004012e2 e811080000 call lstrcpyA
004012e7 6817c94000 push 0x40c917 {"-OMGWTFBBQ"}
004012ec 681de74000 push 0x40e71d
004012f1 e8f6070000 call lstrcatAOnce again, let’s sum all of this up (with an example):
| Steps | Example |
|---|---|
| 1. The Name EditBox value is retrieved. | name = crackmes_are_fun |
2. The length of the Name value (name_len) is calculated, multiplied by 0x539, and converted into an uppercase hexadecimal string (hex_name_length). | name_len = 16 name_len_multiplied = 16 * 0x539 = 0x5390 hex_name_length = 5390 |
3. The Name value is converted into a hexadecimal string, then concatenated with hex_name_length to form hex_name_and_length. | hex_name = 637261636B6D65735F6172655F66756E hex_name_and_length = 637261636B6D65735F6172655F66756E5390 |
4. Blowfish encryption is applied to the hex_name_and_length string, resulting in the blowfish_output. | blowfish_output = 0x2D8CC2A1AEA88C10 |
5. The blowfish_output is converted into a hexadecimal string, hex_blowfish. If name_len > 8, zero-padding is added to the end of the string. | hex_blowfish = 2D8CC2A1AEA88C10 Since nameLen > 8, add zero-paddings: hex_blowfish = 2D8CC2A1AEA88C100000000000000000. |
6. The hex_blowfish string is then processed through the SHA-256 hashing function, and the string OMGWTFBBQ is appended to produce the final serial key. | hashed_blowfish = 013C488CD032A96B4E9BEA7A68897B92D1EFC7404B13E8252CE9C09A7EBC175C serial = 013C488CD032A96B4E9BEA7A68897B92D1EFC7404B13E8252CE9C09A7EBC175C-OMGWTFBBQ |
After verifying that the logic is correct, we can create a keygen for any input name!
Let’s Create A Keygen
I think turning the above logic into a keygen in Python should be straighforward. I’ve written a part of it here, using variable names and comments similar to my above analysis:
import hashlib
def blowfish_encrypt(input_string):
block = input_string[:8] # take only first 8 bytes
eax = int.from_bytes(block[:4], 'big') # load first 4 bytes of input into eax
edx = int.from_bytes(block[4:], 'big') # load next 4 bytes of input into edx
for _ in range(16):
edi = (eax >> 24) & 0xff # extract the highest byte of eax into edi
esi = (eax >> 16) & 0xff # extract the next-highest byte of eax into esi
ebx = S0[edi] # look up S0[edi] into ebx
ebx += S1[esi] # add S1[esi] to ebx
ebx &= 0xFFFFFFFF
edi = (eax >> 8) & 0xff # extract the next byte of eax into edi
esi = eax & 0xff # extract the lowest byte of eax into esi
ebx ^= S2[edi] # XOR S2[edi] with ebx
ebx &= 0xFFFFFFFF
ebx += S3[esi] # add S3[esi] to ebx
ebx &= 0xFFFFFFFF
ebx ^= edx # XOR edx with ebx
edx = eax & 0xFFFFFFFF
eax = ebx & 0xFFFFFFFF
eax, edx = edx, eax # swap eax and edx
# write back the result
return eax.to_bytes(4, 'big') + edx.to_bytes(4, 'big')
def generate_serial(name):
name = name.encode('ascii')
name_len = len(name)
name_len_multiplied = name_len * 1337
hex_name_length = f"{name_len_multiplied:X}" # convert to hex string
hex_name = ''.join(f"{b:02X}" for b in name)
hex_name_and_length = hex_name + hex_name_length
blowfish_output = blowfish_encrypt(hex_name_and_length.encode('ascii'))
blowfish_output = blowfish_output.ljust(name_len, b'\x00') # this is the padding part
hex_blowfish = ''.join(f"{b:02X}" for b in blowfish_output)
hashed_blowfish = hashlib.sha256(hex_blowfish.encode('ascii')).hexdigest().upper()
serial = hashed_blowfish + "-OMGWTFBBQ"
return serial
print(generate_serial("crackmes_are_fun"))The script can’t be run yet, since it’s missing the S-boxes in S0, S1, S2, and S3. Let’s just straight up copying the S-boxes from this Blowfish implementation:
The keygen is now completed!
import hashlib
S0 = [
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a
]
S1 = [
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, 0x8d948e4e, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7
]
S2 = [
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0
]
S3 = [
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
]
def blowfish_encrypt(input_string):
block = input_string[:8] # take only first 8 bytes
eax = int.from_bytes(block[:4], 'big') # load first 4 bytes of input into eax
edx = int.from_bytes(block[4:], 'big') # load next 4 bytes of input into edx
for _ in range(16):
edi = (eax >> 24) & 0xff # extract the highest byte of eax into edi
esi = (eax >> 16) & 0xff # extract the next-highest byte of eax into esi
ebx = S0[edi] # look up S0[edi] into ebx
ebx += S1[esi] # add S1[esi] to ebx
ebx &= 0xFFFFFFFF
edi = (eax >> 8) & 0xff # extract the next byte of eax into edi
esi = eax & 0xff # extract the lowest byte of eax into esi
ebx ^= S2[edi] # XOR S2[edi] with ebx
ebx &= 0xFFFFFFFF
ebx += S3[esi] # add S3[esi] to ebx
ebx &= 0xFFFFFFFF
ebx ^= edx # XOR edx with ebx
edx = eax & 0xFFFFFFFF
eax = ebx & 0xFFFFFFFF
eax, edx = edx, eax # swap eax and edx
# write back the result
return eax.to_bytes(4, 'big') + edx.to_bytes(4, 'big')
def generate_serial(name):
name = name.encode('ascii')
name_len = len(name)
name_len_multiplied = name_len * 1337
hex_name_length = f"{name_len_multiplied:X}" # convert to hex string
hex_name = ''.join(f"{b:02X}" for b in name)
hex_name_and_length = hex_name + hex_name_length
blowfish_output = blowfish_encrypt(hex_name_and_length.encode('ascii'))
blowfish_output = blowfish_output.ljust(name_len, b'\x00') # this is the padding part
hex_blowfish = ''.join(f"{b:02X}" for b in blowfish_output)
hashed_blowfish = hashlib.sha256(hex_blowfish.encode('ascii')).hexdigest().upper()
serial = hashed_blowfish + "-OMGWTFBBQ"
return serial
print(generate_serial("crackmes_are_fun"))Collisions?
Well this is actually an unintended part LOL. I just discovered (by accident) that the key construction algorithm is susceptible to collisions right while I was writing the explanation for the Blowfish implementation!
The idea is that our Blowfish encryption above only processes the first 8 bytes of the hex_name_and_length string, which correspond to the hexadecimal representation of the first 4 characters of the input name. Also, the padding and hex conversion for the SHA-256 input depend just on the name's length. So if two names start with the same 4 characters and have the same total length, they'll generate the same serial number.
Try running the script with names like overwrite_instruction_pointer and overcome_assembly_obfuscation. They should yield the same serial!
Thoughts
I've been relying on decompilers when I'm working through problems, and honestly, raw Assembly still makes me uncomfortable. So I figured this crackme was a good opportunity to force myself to actually understand it.
I ended up spending hours going through the functions line by line. But you know what? Writing this whole thing up actually helped. Breaking it down and explaining it made Assembly feel way less intimidating than it used to. I'm starting to think I might actually get this now!
Kudos to the author for hand-crafting such an educational crackme!























