Tearing apart a serial scheme
Written by Sphinx
Ok. Let's snoop! But we get no important hits doing that. So my first move already failed. Anyway...
- Run it (sure!). Notice how it greets you. Yep, with a (nice) nag along with the typical "enter-name-and-serial" type.
Ok. I entered Sphinx as name and 12345 as serial. The hit "Ok". But hey! It's wrong! So how do we get around it?
Ok. First, let's snoop and find out how :) Ahhh, ok. Notice that in the Help > About Softy sub-menu, we get the same
dialog. So let's try breakpointing here. Yeah, I know what you're (probably) thinking, let's breakpoint "GetWindowTextA"
or "GetDlgItemTextA". But lo! Notice that it's a win16 phile! So if you use those, you should remove the A's on the API
names. Anywho, I didn't use them but "hmemcpy" instead ;) Btw, it's "GetDlgItemText" :)
- In SoftICE, type: bpx hmemcpy [enter]
- Press F5 (or Ctrl-D) to exit SoftICE and return back to our prey
- Hit "Ok". Then... break!
Ok. Not yet in a good spot, so we've to mash F12 several times (here, about 8). Ok. Now we're in the app's code.
But hey! The code (not shown) seems to be just putting your name and (fake) serial in a [softy.ini] in C:\WINDOWS :)
Yep, through "WritePrivateProfileString". And here's my snippet (with comments):
[Registration] ; section name
User=Sphinx ; real username :)
Key=12345 ; fake usercode
So what now? We've to breakpoint the opposite API which is "GetPrivateProfileString". (remember, win16?). Ok. But we need to run anew to break here. Why? Since it's most likely be reading the values at startup, les, it would be useless! And besides, doing this in the About menu is also useless. So exit Softy then.
- bpx "GetPrivateProfileString" [enter]
- Run anew. Then... break! We could press F12 once and we'll be back to the program's code.
But notice that the first one isn't that interesting at all coz the ones we need aren't read yet :) So, press F5 and hope that
we'll break on the next call to the API. After breaking, we've to mash F12 again.
You see: (as exported from SoftICE - with "CODE OFF" :)
2EF7:0112 CALL KERNEL!GETPRIVATEPROFILESTRING
2EF7:0117 CMP BYTE PTR [BP-28],00 ; is there NO username at all?
2EF7:011B JZ 0123 ; yes? jump, else...
2EF7:011D CMP BYTE PTR [BP-48],00 ; is there a usercode at all?
2EF7:0121 JNZ 0125 ; yes? jump, else...
2EF7:0123 JMP 01A0 ; jump to end (with badflag!)
Ok. Since we've entered some, we'll jump!
2EF7:0125 MOV DWORD PTR [BP-04],00000000 ; initial value
2EF7:012D XOR SI,SI ; clear si
2EF7:012F JMP 0154 ; jump first
2EF7:0131 CMP SI,20 ; is si >= 20? (here, ours is 0 first)
2EF7:0134 JGE 015A ; yes? jump, else... here, so NO JUMP!
Ok. Soooh! If namelength is >= 20, discard the rest, use only the first 1Fh chars for the calcs!
2EF7:0136 MOV AX,SI
2EF7:0138 MOV CX,0004
2EF7:013C IDIV CX
2EF7:013E MOV AL,[BP+SI-28]
2EF7:0142 MOV CX,DX
2EF7:0145 SHL CL,03
2EF7:0148 CALL 2F57:8662
2EF7:014D ADD [BP-04],AX
2EF7:0150 ADC [BP-02],DX
2EF7:0153 INC SI
2EF7:0154 CMP BYTE PTR [BP+SI-28],00
2EF7:0158 JNZ 0131
Hiew! Too long without comments? Hey! Anyway, here's how it REALLY goes:
#1: Define a table of type dword (4 bytes) (let's call it "table" ;)
#2: Read name[i] and add it with table[i] value then place it in table[i]
ie. (remember that the table initially had 0's!)
name = 53 ("S") -- 53 + 0 = table = 53
name = 70 ("p") -- 70 + 0 = table = 70
name = 68 ("h") -- 68 + 0 = table = 68
name = 69 ("i") -- 69 + 0 = table = 69
#3: If the table's already full (all 4 bytes were filled) and there's still chars left, go back to table and carry on.
name = 6E ("n") -- 6E + 53 = table = C1
name = 78 ("x") -- 78 + 70 = table = E8
And since it's the end, my table now is C1E86869. Yep, that's my [bp-04]. And now onto the REAL calculation part...
2EF7:015A XOR DWORD PTR [BP-04],55555555; ; xor table with 55555555
[bp-04] = [bp-04] xor 55555555
= 6968E8C1 xor 55555555 ; it's 6968E8C1 coz it's read backwards!
[bp-04] = 3C3DBD94
2EF7:0162 ADD DWORD PTR [BP-04],55555555 ; then add 55555555
[bp-04] = [bp-04] + 55555555
= 3C3DBD94 + 55555555
[bp-04] = 919312E9
2EF7:016A MOV EAX,[BP-04] ; eax = 919312E9
2EF7:016E MOV ECX,05F5E100 ; ecx = 5F5E100 (divisor)
2EF7:0174 XOR EDX,EDX
2EF7:0177 DIV ECX ; get the remainder
edx = edx % ecx ; the "%" is for getting the remainder (as in C)
= 919312E9 % 5F5E100
edx = 285FAE9 (which is 423349?? in dec)
Yep, we can sniff the serial now. But I guess it's kinda important to know how it is compared noh? ;) So we continue...
2EF7:017A MOV EAX,EDX ; make a copy of the remainder...
2EF7:017D MOV [BP-08],EAX ; and save it to [bp-08]
2EF7:0181 LEA AX,[BP-48] ; address of usercode (ie. 12345)
2EF7:0184 PUSH AX ; save it first then...
2EF7:0185 CALL 2F57:7494 ; convert it to hex
2EF7:018A POP BX ; pop usercode address
2EF7:018B XOR AX,[BP-08] ; for the comparison below! Here, the result is NOT 0!
2EF7:018E XOR DX,[BP-06] ; as above. And again, result is NOT 0!
Why do I keep blabbering about them NOT being 0?! Because of the special comparison scheme the programs uses...
2EF7:0191 OR DX,AX ; is dx = ax (by being the same)?
2EF7:0193 JNZ 01A0 ; nope! jump, else...
So to be a partial goodboy, ax and dx should be equal. How? They must be both 0 when they're "xored" above. Next...
2EF7:0195 CMP SI,09 ; what's this? Since it counted the namelength to si, mine's 6, and...
2EF7:0198 JL 01A0 ; since it's < 9, i'm STILL a badboy!
See? The importance of making sure how the program compares it is now evident! And if we haven't known it, we'll (still)
be scratching our head thinking why the serial we've sniffed didn't work!
So we now know that the name should be >= 9 chars! Hmph! Ok. So I changed it to my full nick :) So I replaced it with Sphinxter (cute noh?). Ok. Since I've changed my entire name entry, all of the values above will also change. Yep,
the table and the values from xoring and adding with 55555555. Anyway, here's the summary:
Table xor 55555555: CEDCE933 xor 55555555 = 9B89BC66
Table add 55555555: 9B89BC66 + 55555555 = F0DF11BB
Table mod 05F5E100: F0DF11BB mod 05F5E100 = 0273E9BB (which is 411509?? in decimal)
So if i entered that (the decimal equiv), we'll go directly here:
2EF7:019A MOV AX,0001 ; ax=1 (goodflag)
2EF7:019D POP SI
2EF7:01A0 XOR AX,AX ; ax=0 (badflag)
2EF7:01A2 POP SI
Well, well. That's it! No more nag (and disabled features!?). Hmmm.. that must be one of the longest serial sniffing huh? :) Too much for a win16 app ;) So I guess that's it, cya!