160个crackme 之 81 --- tsc RSA-24注册分析和爆破注册机算码
本帖最后由 solly 于 2019-4-11 13:12 编辑160个crackme 之 81 --- tsc RSA-24注册分析和爆破注册机算码
在160 crackme合集中没有看到有 81 tsc 的分析过程,因此再拿这个分析一下,与各位分享。
首先説明一下,这个crackme的about中説这个是一个 RSA-24加密,分析得到的加密过程主要就是一个乘积加取余运算的循环。并且代码中有一些无用的调用和赋值代码,不过这些干扰代码不复杂,反而影响比较大的是所有的运算调用都是传字符串参数进去,再输出结果字符串,导致大部分代码都是对字符串计算长度,检查是否全数字,再一堆的字符串复制、移动、转换等操作,真正的运算过程很难找到,而且call调用层次太深,全进去分析不太可能,所以分析时需要控制一定层次并根据输入输出来判断这些调用的功能。而RSA中的大数运算,因为精度太大,一般也都是以字符串作为参数来处理,我认为这个crackme应该是使了用某个数学库来进行地运算,而数学库一般比较杂复,也会导致调用层次过多,因此,去分析数学库没什么意义,只有在调用完后,根据运算结果字符串来判断这个调用的功能,这个才是关键,不然,就会被绕晕去。
还有就是crackme会将输入的需入的序列号分成两个7字节的两组数字分别加密运算,运算完成后与crackme内的两个常量比较来完成校验,并且只要有一组通过检测就认为序列号是对的,因此,序列号是一个14位的十进制的大整数,后面各种运算,也是传入的十进制整数字符串作为参数。
最后就是注册名不参与加密运算,只是用来提示注册成功时用一下。
先检查一下有没有壳吧:
没有壳,运行crackme,先看看界面和错误提示。
输入假码“7878787878”,会有提示“Try again !”,表示序列号不对。
用OD载入,直接打开字符串查找功能搜索"Try again !":
马上找到了"Try again !",双击来到引用该字符串的代码处:
看到有一个跳转直接来到这行代码,直接代码回溯,如下:
来到跳转处:
其汇编代码如下:
00402840 .83EC 58 sub esp,0x58
00402843 .A1 B4004200 mov eax,dword ptr ds: ;将"RSA Crackme"前4个字符当Hex数存入EAX, eax=0x20415352
00402848 .8B15 BC004200 mov edx,dword ptr ds: ;将"kme"和null结束符共4字节当作Hex数存入EDX, edx=0x00656D6B
0040284E .53 push ebx ;ebx = 1
0040284F .8BD9 mov ebx,ecx ;ecx = 0x0019FE58,应该是个地址
00402851 .8B0D B8004200 mov ecx,dword ptr ds: ;将"Crackme"前4个字节当作Hex数存入ECX, ecx=0x63617243
00402857 .894424 04 mov dword ptr ss:,eax ;保存eax
0040285B .8D4424 24 lea eax,dword ptr ss:
0040285F .6A 0F push 0xF ;BufferLength: 15
00402861 .894C24 0C mov dword ptr ss:,ecx ;拼接字符串
00402865 .50 push eax ;接收注册码的缓冲区
00402866 .68 EB030000 push 0x3EB ;DlgItem ID: 1003,对话框资源ID
0040286B .8BCB mov ecx,ebx ;ecx = 0x0019FE58,参数1, this指针
0040286D .895424 18 mov dword ptr ss:,edx ;将 eax, ecx, edx 拼接成字符串"RSA Crackme"
00402871 .E8 720F0100 call tsc.004137E8 ;GetDlgItemTextA
; 注册码校验过程调用
00402876 .8D4C24 24 lea ecx,dword ptr ss: ;ECX==>假码“7878787878”
0040287A .51 push ecx ;参数2,注册码假码地址
0040287B .8BCB mov ecx,ebx ;ecx = 0x0019FE58,参数1,this指针
0040287D .E8 2E010000 call tsc.004029B0 ;注册码校验过程,EAX返回0表示校验失败了
00402882 .85C0 test eax,eax ;检查注册码校验结果
00402884 .0F84 88000000 je tsc.00402912 ;如果返回为0,跳转去显示注册失败,否则不跳转显示注册成功!
;以下代码用来显示注册成功
0040288A .A1 AC004200 mov eax,dword ptr ds: ; done,
0040288F .8B0D B0004200 mov ecx,dword ptr ds: ;e,
00402895 .8B15 A8004200 mov edx,dword ptr ds: ;well done,
0040289B .57 push edi
0040289C .894424 3C mov dword ptr ss:,eax
004028A0 .894C24 40 mov dword ptr ss:,ecx
004028A4 .56 push esi
004028A5 .895424 3C mov dword ptr ss:,edx ;生成 "well done, " 字符串
004028A9 .B9 07000000 mov ecx,0x7
004028AE .33C0 xor eax,eax
004028B0 .8D7C24 48 lea edi,dword ptr ss:
004028B4 .55 push ebp
004028B5 .8D5424 1C lea edx,dword ptr ss:
004028B9 .6A 14 push 0x14
004028BB .F3:AB rep stos dword ptr es:
004028BD .52 push edx
004028BE .68 E9030000 push 0x3E9
004028C3 .8BCB mov ecx,ebx
004028C5 .E8 1E0F0100 call tsc.004137E8 ;GetDlgItemTextA
004028CA .8D7C24 1C lea edi,dword ptr ss: ;EDI==>"solly"
004028CE .83C9 FF or ecx,-0x1 ;计数器,ecx=0xFFFFFFFF
004028D1 .33C0 xor eax,eax ;'\0'
004028D3 .8D5424 40 lea edx,dword ptr ss: ;edx==>"well done, "
004028D7 .F2:AE repne scas byte ptr es:
004028D9 .F7D1 not ecx ;ecx=注册名len+1
004028DB .2BF9 sub edi,ecx ;恢复edi指向"solly"
004028DD .6A 40 push 0x40
004028DF .8BF7 mov esi,edi
004028E1 .8BE9 mov ebp,ecx ;ebp=长度+1
004028E3 .8BFA mov edi,edx ;edi==>"well done, "
004028E5 .83C9 FF or ecx,-0x1 ;ecx=0xFFFFFFFF
004028E8 .F2:AE repne scas byte ptr es:
004028EA .8BCD mov ecx,ebp ;ecx=ebp=注册名长度+1
004028EC .4F dec edi ;edi指向"well done, "的后面
004028ED .C1E9 02 shr ecx,0x2 ;开始将注册名,复制到 "well done, "字符串后面,用于提示成功注册!
004028F0 .F3:A5 rep movs dword ptr es:,dword ptr ds:
004028F2 .8BCD mov ecx,ebp
004028F4 .8D4424 14 lea eax,dword ptr ss:
004028F8 .83E1 03 and ecx,0x3
004028FB .50 push eax
004028FC .F3:A4 rep movs byte ptr es:,byte ptr ds:
004028FE .8D4C24 48 lea ecx,dword ptr ss: ;ecx==>"well done, solly"
00402902 .51 push ecx
00402903 .8BCB mov ecx,ebx
00402905 .E8 B8350100 call tsc.00415EC2 ;AfxMessageBox,显示注册成功!
0040290A .5D pop ebp ;0019F5EC
0040290B .5E pop esi ;0019F5EC
0040290C .5F pop edi ;0019F5EC
0040290D .5B pop ebx ;0019F5EC
0040290E .83C4 58 add esp,0x58
00402911 .C3 retn
;以下代码用来显示注册失败
00402912 >8B15 9C004200 mov edx,dword ptr ds: ;Try again !
00402918 .A1 A0004200 mov eax,dword ptr ds: ;again !
0040291D .8B0D A4004200 mov ecx,dword ptr ds: ;n !
00402923 .895424 10 mov dword ptr ss:,edx
00402927 .894424 14 mov dword ptr ss:,eax
0040292B .8D5424 04 lea edx,dword ptr ss:
0040292F .6A 30 push 0x30
00402931 .8D4424 14 lea eax,dword ptr ss:
00402935 .894C24 1C mov dword ptr ss:,ecx
00402939 .52 push edx
0040293A .50 push eax
0040293B .8BCB mov ecx,ebx
0040293D .E8 80350100 call tsc.00415EC2 ;AfxMessageBox,显示注册失败
00402942 .5B pop ebx ;0019F5EC
00402943 .83C4 58 add esp,0x58
00402946 .C3 retn
00402947 90 nop
00402948 90 nop
上面代码分析写在注释中了,其中 call tsc.004029B0 就是进行序列号加密运算的过程,并且这段代码引用了一块内存,保存了crackme需要的一些常量字符串,如下:
;00420088:00 00 00 00 00 00 00 00 30 00 00 00 31 00 00 00........0...1...
;00420098:32 00 00 00 54 72 79 20 61 67 61 69 6E 20 21 002...Try again !.
;004200A8:77 65 6C 6C 20 64 6F 6E 65 2C 20 00 52 53 41 20well done, .RSA
;004200B8:43 72 61 63 6B 6D 65 00 35 36 36 36 39 33 33 00Crackme.5666933.
;004200C8:38 34 38 33 36 37 38 00 31 32 37 39 30 38 39 318483678.12790891
;004200D8:00 00 00 00 39 39 30 31 00 00 00 00 00 00 00 00....9901........
下面是进入 call tsc.004029B0 代码分析,这是序列号的主要加密运算过程:
004029B0/$6A FF push -0x1
004029B2|.68 189E4100 push tsc.00419E18 ;SE 处理程序安装
004029B7|.64:A1 00000000 mov eax,dword ptr fs:
004029BD|.50 push eax ;备份SEH
004029BE|.64:8925 00000000 mov dword ptr fs:,esp ;更新SEH
004029C5|.81EC 50060000 sub esp,0x650
004029CB|.56 push esi ;tsc.0041B100
004029CC|.57 push edi
;以下是将4个常量字符串赋值
004029CD|.68 DC004200 push tsc.004200DC ;指向字符串:"9901"
004029D2|.8D8C24 E4000000lea ecx,dword ptr ss: ;存放字符串的缓冲区
004029D9|.E8 52E7FFFF call tsc.00401130 ;__strmove(char * dst, char * src)
004029DE|.68 D0004200 push tsc.004200D0 ;指向字符串:"12790891"
004029E3|.8D4C24 1C lea ecx,dword ptr ss:
004029E7|.C78424 64060000 >mov dword ptr ss:,0x0 ;=0x0
004029F2|.E8 39E7FFFF call tsc.00401130 ;__strmove(char * dst, char * src)
004029F7|.68 C8004200 push tsc.004200C8 ;指向字符串:"8483678"
004029FC|.8D8C24 74020000lea ecx,dword ptr ss:
00402A03|.C68424 64060000 >mov byte ptr ss:,0x1 ;=0x1
00402A0B|.E8 20E7FFFF call tsc.00401130 ;__strmove(char * dst, char * src)
00402A10|.68 C0004200 push tsc.004200C0 ;指向字符串:"5666933"
00402A15|.8D8C24 AC010000lea ecx,dword ptr ss:
00402A1C|.C68424 64060000 >mov byte ptr ss:,0x2 ;=0x2
00402A24|.E8 07E7FFFF call tsc.00401130 ;__strmove(char * dst, char * src)
;以下是检查注册码的长度,不等14则失败
00402A29|.8B9424 68060000mov edx,dword ptr ss: ;edx==>假码“7878787878”
00402A30|.83CE FF or esi,-0x1 ;ESI=0xFFFFFFFF
00402A33|.8BFA mov edi,edx
00402A35|.8BCE mov ecx,esi ;tsc.0041B100
00402A37|.33C0 xor eax,eax ;'\0',用于下面字符串扫描
00402A39|.C68424 60060000 >mov byte ptr ss:,0x3 ;=0x3,前面没有push,所以实际是
00402A41|.F2:AE repne scas byte ptr es: ;扫描假码,计算其长度
00402A43|.F7D1 not ecx
00402A45|.49 dec ecx ;ecx为其长度
00402A46|.83F9 0E cmp ecx,0xE ;长度是否为14字节
00402A49|.0F85 63010000 jnz tsc.00402BB2 ;不等于14字节则结束校验,注册校验失败
;以下检查注册码是否全部是数字,不全是则退出,注册失败
00402A4F|.33C9 xor ecx,ecx ;int i=0
00402A51|>8A0411 /mov al,byte ptr ds: ;al = edx,取出输入的注册码,判断是否数字
00402A54|.3C 30 |cmp al,0x30 ;al<'0'
00402A56|.0F8C 56010000 |jl tsc.00402BB2 ;小于'0',非数字,注册校验失败
00402A5C|.3C 39 |cmp al,0x39 ;al>'9'
00402A5E|.0F8F 4E010000 |jg tsc.00402BB2 ;大于'9',非数字,注册校验失败
00402A64|.41 |inc ecx ;i++
00402A65|.83F9 0E |cmp ecx,0xE ;i<14
00402A68|.^ 7C E7 \jl short tsc.00402A51 ;循环,while(i<14)
;开始将注册码平均分成2个7字节长度的注册码,分成两组进行校验,只要一组通过即表示注册成功
00402A6A|.8BC2 mov eax,edx ;eax==>"78787878787878"
00402A6C|.C64424 17 00 mov byte ptr ss:,0x0
00402A71|.C64424 0F 00 mov byte ptr ss:,0x0
00402A76|.8B08 mov ecx,dword ptr ds: ;ecx=注册码前4字节的ascii码组成的数字
00402A78|.894C24 10 mov dword ptr ss:,ecx ;保存第1个注册码子字符串
00402A7C|.66:8B48 04 mov cx,word ptr ds: ;第5,6字节
00402A80|.66:894C24 14 mov word ptr ss:,cx ;连接到前面保存的"7878"后面,形成"787878"
00402A85|.8B4A 07 mov ecx,dword ptr ds: ;ecx=第8,9,10,11位,跳过了第7位
00402A88|.8A40 06 mov al,byte ptr ds: ;al=第7位
00402A8B|.894C24 08 mov dword ptr ss:,ecx ;保存第2个注册码子字符串
00402A8F|.884424 16 mov byte ptr ss:,al ;接到前面拼接的"787878"后面,形成"7878787"
00402A93|.8D42 07 lea eax,dword ptr ds: ;eax==>输入的注册码第8位假码位置
00402A96|.8D4C24 10 lea ecx,dword ptr ss: ;ecx==>拼接的7个字节的"7878787"
00402A9A|.66:8B50 04 mov dx,word ptr ds: ;第12,13位置的"87"
00402A9E|.8A40 06 mov al,byte ptr ds: ;第14位置"8"
00402AA1|.51 push ecx ;ecx==>"7878787",第1个子字符串,第2个参数 char * src
00402AA2|.8D8C24 94050000lea ecx,dword ptr ss: ;ecx为第1个参数 char * dst
00402AA9|.66:895424 10 mov word ptr ss:,dx ;拼接到第2个子字符串
00402AAE|.884424 12 mov byte ptr ss:,al ;形成第2个子字符串"8787878"
;开始注册码的运算
00402AB2|.E8 79E6FFFF call tsc.00401130 ;__strmove(char * dst, char * src)
00402AB7|.8D5424 18 lea edx,dword ptr ss: ;edx==>"12790891"
00402ABB|.8D8424 E0000000lea eax,dword ptr ss: ;eax==>"9901"
00402AC2|.52 push edx
00402AC3|.8D8C24 04040000lea ecx,dword ptr ss: ;ecx=0x0019F37C,==>保存返回值
00402ACA|.50 push eax
00402ACB|.51 push ecx
00402ACC|.8D8C24 9C050000lea ecx,dword ptr ss: ;ecx==>"7878787", 参数1
00402AD3|.C68424 6C060000 >mov byte ptr ss:,0x4
00402ADB|.E8 30F8FFFF call tsc.00402310 ;对参数进行求积求余运算,返回eax==>"597922"
00402AE0|.8D5424 08 lea edx,dword ptr ss: ;edx==>"8787878",第2部分注册码
00402AE4|.8D8C24 38030000lea ecx,dword ptr ss: ;char * dst
00402AEB|.52 push edx ;char * src
00402AEC|.C68424 64060000 >mov byte ptr ss:,0x5
00402AF4|.E8 37E6FFFF call tsc.00401130 ;__strmove, 将注册码第2部分,复制到
00402AF9|.8D4424 18 lea eax,dword ptr ss: ;eax==>"12790891"
00402AFD|.8D8C24 E0000000lea ecx,dword ptr ss: ;ecx==>"9901"
00402B04|.50 push eax ;参数4,"12790891"
00402B05|.8D9424 CC040000lea edx,dword ptr ss: ;edx=0x0019F444,返回缓冲区
00402B0C|.51 push ecx ;参数3,"9901"
00402B0D|.52 push edx ;参数2,返回缓冲区
00402B0E|.8D8C24 44030000lea ecx,dword ptr ss: ;参数1,注册码的第2部分
00402B15|.C68424 6C060000 >mov byte ptr ss:,0x6
00402B1D|.E8 EEF7FFFF call tsc.00402310 ;对参数进行求积求余运算,返回eax=>"10902440"
; 两组注册码运算结果与系统的常量比较,检查注册码是否合法
00402B22|.8D8424 70020000lea eax,dword ptr ss: ;eax==>"8483678",系统常量
00402B29|.8D8C24 00040000lea ecx,dword ptr ss: ;参数1,ecx==>"597922",第1次返回的余数
00402B30|.50 push eax ;参数2,系统常量"8483678"
00402B31|.C68424 64060000 >mov byte ptr ss:,0x7
00402B39|.E8 82F2FFFF call tsc.00401DC0 ;字符串比较
00402B3E|.85C0 test eax,eax
00402B40|.0F84 BF000000 je tsc.00402C05 ;如果EAX==0,校难成功,跳转
00402B46|.8D8C24 A8010000lea ecx,dword ptr ss: ;ecx==>"5666933",系统常量
00402B4D|.51 push ecx ;参数2,系统常量"5666933"
00402B4E|.8D8C24 CC040000lea ecx,dword ptr ss: ;参数1,ecx==>"10902440",第2次返回的余数
00402B55|.E8 66F2FFFF call tsc.00401DC0 ;字符串比较
00402B5A|.85C0 test eax,eax
00402B5C|.0F84 A3000000 je tsc.00402C05 ;如果EAX==0,校难成功,跳转
;以下是校验失败时执行的代码,很多空函数和无用的赋值,算了干扰代码吧
00402B62|.8D8C24 C8040000lea ecx,dword ptr ss:
00402B69|.C68424 60060000 >mov byte ptr ss:,0x6
00402B71|.E8 EAE5FFFF call tsc.00401160 ;空函数
00402B76|.8D8C24 38030000lea ecx,dword ptr ss:
00402B7D|.C68424 60060000 >mov byte ptr ss:,0x5
00402B85|.E8 D6E5FFFF call tsc.00401160 ;空函数
00402B8A|.8D8C24 00040000lea ecx,dword ptr ss:
00402B91|.C68424 60060000 >mov byte ptr ss:,0x4
00402B99|.E8 C2E5FFFF call tsc.00401160 ;空函数
00402B9E|.8D8C24 90050000lea ecx,dword ptr ss:
00402BA5|.C68424 60060000 >mov byte ptr ss:,0x3
00402BAD|.E8 AEE5FFFF call tsc.00401160 ;空函数,长度校验或数字校验失败会跳转来到这里
00402BB2|>8D8C24 A8010000lea ecx,dword ptr ss: ;ecx==>"5666933"
00402BB9|.C68424 60060000 >mov byte ptr ss:,0x2
00402BC1|.E8 9AE5FFFF call tsc.00401160 ;空函数
00402BC6|.8D8C24 70020000lea ecx,dword ptr ss: ;ecx==>"8483678"
00402BCD|.C68424 60060000 >mov byte ptr ss:,0x1
00402BD5|.E8 86E5FFFF call tsc.00401160 ;空函数
00402BDA|.8D4C24 18 lea ecx,dword ptr ss: ;ecx==>"12790891"
00402BDE|.C68424 60060000 >mov byte ptr ss:,0x0
00402BE6|.E8 75E5FFFF call tsc.00401160 ;空函数
00402BEB|.8D8C24 E0000000lea ecx,dword ptr ss: ;ecx==>"9901"
00402BF2|.89B424 60060000mov dword ptr ss:,esi ;tsc.0041B100
00402BF9|.E8 62E5FFFF call tsc.00401160 ;空函数
00402BFE|.33C0 xor eax,eax ;校验失败,返回0
00402C00|.E9 A1000000 jmp tsc.00402CA6
;以下是校验成功时执行的代码,很多空函数和无用的赋值,算了干扰代码吧
00402C05|>8D8C24 C8040000lea ecx,dword ptr ss:
00402C0C|.C68424 60060000 >mov byte ptr ss:,0x6
00402C14|.E8 47E5FFFF call tsc.00401160 ;空函数
00402C19|.8D8C24 38030000lea ecx,dword ptr ss:
00402C20|.C68424 60060000 >mov byte ptr ss:,0x5
00402C28|.E8 33E5FFFF call tsc.00401160 ;空函数
00402C2D|.8D8C24 00040000lea ecx,dword ptr ss:
00402C34|.C68424 60060000 >mov byte ptr ss:,0x4
00402C3C|.E8 1FE5FFFF call tsc.00401160 ;空函数
00402C41|.8D8C24 90050000lea ecx,dword ptr ss:
00402C48|.C68424 60060000 >mov byte ptr ss:,0x3
00402C50|.E8 0BE5FFFF call tsc.00401160 ;空函数
00402C55|.8D8C24 A8010000lea ecx,dword ptr ss:
00402C5C|.C68424 60060000 >mov byte ptr ss:,0x2
00402C64|.E8 F7E4FFFF call tsc.00401160 ;空函数
00402C69|.8D8C24 70020000lea ecx,dword ptr ss:
00402C70|.C68424 60060000 >mov byte ptr ss:,0x1
00402C78|.E8 E3E4FFFF call tsc.00401160 ;空函数
00402C7D|.8D4C24 18 lea ecx,dword ptr ss:
00402C81|.C68424 60060000 >mov byte ptr ss:,0x0
00402C89|.E8 D2E4FFFF call tsc.00401160 ;空函数
00402C8E|.8D8C24 E0000000lea ecx,dword ptr ss:
00402C95|.89B424 60060000mov dword ptr ss:,esi ;tsc.0041B100
00402C9C|.E8 BFE4FFFF call tsc.00401160 ;空函数
00402CA1|.B8 01000000 mov eax,0x1 ;只要两次比较,有一次相等则校验成功,返回1
;公用退出函数代码
00402CA6|>8B8C24 58060000mov ecx,dword ptr ss: ;取出备份的SEH
00402CAD|.5F pop edi ;tsc.00402882
00402CAE|.64:890D 00000000 mov dword ptr fs:,ecx ;恢复SEH
00402CB5|.5E pop esi ;tsc.00402882
00402CB6|.81C4 5C060000 add esp,0x65C
00402CBC\.C2 0400 retn 0x4
00402CBF 90 nop
其中关键两个地方就是两次 call tsc.00402310 的调用,这个call就是用来加密运算的,其它代码主要是一些字符串的操作,以及一些无用代码。
下面是 call tsc.00402310 分析,这也是最后一层的调用分析,这个调用内的call就不要再深入分析,都是数学库的大数计算例程,极为复杂,只要猜对这些调用的功能即可:
00402310/$6A FF push -0x1
00402312|.68 0C9D4100 push tsc.00419D0C ;SE 处理程序安装
00402317|.64:A1 00000000 mov eax,dword ptr fs:
0040231D|.50 push eax ;备份SEH指针
0040231E|.64:8925 00000000 mov dword ptr fs:,esp ;保存新的SEH指针
00402325|.81EC B4040000 sub esp,0x4B4
0040232B|.53 push ebx
0040232C|.56 push esi
0040232D|.8BF1 mov esi,ecx ;参数1,指向注册码
0040232F|.8D8424 9C010000lea eax,dword ptr ss: ;用于保存"9901"的二进制码字符串
00402336|.8B8C24 D0040000mov ecx,dword ptr ss: ;ecx==>"9901",下面调用的参数1
0040233D|.57 push edi ;ntdll.778B09E0
0040233E|.50 push eax ;下面调用的参数2,输出缓冲区
0040233F|.C74424 10 000000>mov dword ptr ss:,0x0
00402347|.E8 A4FDFFFF call tsc.004020F0 ;将10进制的"9901"转换成2进制的:"10011010101101"
0040234C|.68 94004200 push tsc.00420094 ;字符:'1'
00402351|.8D8C24 DC000000lea ecx,dword ptr ss: ;(第1个余数)ecx==>第1个余数,这里初始化为"1"
00402358|.C78424 CC040000 >mov dword ptr ss:,0x1
00402363|.E8 C8EDFFFF call tsc.00401130 ;__strmove(char *dst, char *src)
00402368|.68 98004200 push tsc.00420098 ;字符:'2'
0040236D|.8D8C24 FC030000lea ecx,dword ptr ss: ;保存“2”
00402374|.C68424 CC040000 >mov byte ptr ss:,0x2
0040237C|.E8 AFEDFFFF call tsc.00401130 ;__strmove(char *dst, char *src)
00402381|.56 push esi ;esi==>"7878787", esi=>"8787878"
00402382|.8D4C24 14 lea ecx,dword ptr ss: ;(第2个余数)ecx==>第2个余数,保存注册码,初始化为注册码
00402386|.C68424 CC040000 >mov byte ptr ss:,0x3
0040238E|.E8 9DEDFFFF call tsc.00401130 ;__strmove(char *dst, char *src)
00402393|.8DBC24 A0010000lea edi,dword ptr ss: ;edi==>"9901"的2进制"10011010101101"
0040239A|.83C9 FF or ecx,-0x1 ;计数器,ecx=0xFFFFFFFF
0040239D|.33C0 xor eax,eax ;'\0'
0040239F|.B3 04 mov bl,0x4
004023A1|.F2:AE repne scas byte ptr es:
004023A3|.F7D1 not ecx ;ecx=0x0F,二进制数的长度+1
004023A5|.49 dec ecx ;ecx为2进制字符串的长度,0x0E
004023A6|.889C24 C8040000mov byte ptr ss:,bl ;0x04
004023AD|.8BF1 mov esi,ecx ;ecx=长度
004023AF|.4E dec esi ;长度>0, esi = ecx-1,esi为索引0~len-1
004023B0|.0F88 AB000000 js tsc.00402461
004023B6|.8BBC24 D8040000mov edi,dword ptr ss: ;edi==>"12790891"
004023BD|>80BC34 A0010000 >/cmp byte ptr ss:,0x31 ;9901bin, bin是否为‘1’,为"1"则多计算一轮
004023C5|.75 4E |jnz short tsc.00402415
004023C7|.8D4C24 10 |lea ecx,dword ptr ss: ;(第2个余数)ecx==>"7878787",ecx="624508", ecx="3184583"
004023CB|.51 |push ecx
004023CC|.8D8C24 DC000000|lea ecx,dword ptr ss: ;ecx==>上一轮循环中的第1个余数或"1", ecx="7878787", ecx="3934589"
004023D3|.E8 98F3FFFF |call tsc.00401770 ;求ecx(push ecx)和乘积, eax==>7878787*7878787, eax=7878787*624508
004023D8|.8D9424 30030000|lea edx,dword ptr ss: ;edx==>"7878787"
004023DF|.57 |push edi ;edi==>"12790891"
004023E0|.52 |push edx
004023E1|.8D8C24 E0000000|lea ecx,dword ptr ss: ;ecx==>"7878787", ecx==>"4920365511796"
004023E8|.E8 C3FAFFFF |call tsc.00401EB0 ;求上面的乘积与"12790891"的余数(第1个余数)
004023ED|.50 |push eax ;char * src, eax==>"7878787", eax="3934589"
004023EE|.8D8C24 DC000000|lea ecx,dword ptr ss: ;char * dst, 余数保存在此
004023F5|.C68424 CC040000 >|mov byte ptr ss:,0x5
004023FD|.E8 2EEDFFFF |call tsc.00401130 ;__strmove(char *dst, char *src)
00402402|.8D8C24 30030000|lea ecx,dword ptr ss: ;ecx==>"7878787", ecx="3934589"
00402409|.889C24 C8040000|mov byte ptr ss:,bl
00402410|.E8 4BEDFFFF |call tsc.00401160 ;空函数
00402415|>8D4424 10 |lea eax,dword ptr ss: ;eax==>"7878787", eax="3342634", eax="624508"
00402419|.8D4C24 10 |lea ecx,dword ptr ss: ;ecx=eax==>"7878787", ecx="3342634", ecx="624508"
0040241D|.50 |push eax ;eax==>上一轮循环中的第2个余数或7位拼装的注册码
0040241E|.E8 4DF3FFFF |call tsc.00401770 ;求eax(push eax)和的乘积,两数相等,求平方
00402423|.8D8C24 68020000|lea ecx,dword ptr ss: ;eax==>"62075284591369"=7878787*7878787,求平方
0040242A|.57 |push edi ;edi==>"12790891", ecx==>上面求乘积前的eax
0040242B|.51 |push ecx ;ecx==>前面的乘数eax
0040242C|.8D4C24 18 |lea ecx,dword ptr ss: ;ecx==>"62075284591369", ecx="11173202057956",eax="390010242064"
00402430|.E8 7BFAFFFF |call tsc.00401EB0 ;求上面乘积与"12790891"的余数(第2个余数)
00402435|.50 |push eax ;char * src, eax==>"3342634"= 62075284591369 % 12790891
00402436|.8D4C24 14 |lea ecx,dword ptr ss: ;char * dst,保存余数,这里的与前面为同一地址
0040243A|.C68424 CC040000 >|mov byte ptr ss:,0x6
00402442|.E8 E9ECFFFF |call tsc.00401130 ;__strmove(char *dst, char *src)
00402447|.8D8C24 68020000|lea ecx,dword ptr ss: ;eax==>前面求得的余数,ecx==>"3342634", ecx==>"624508"......,eax="5789538"
0040244E|.889C24 C8040000|mov byte ptr ss:,bl
00402455|.E8 06EDFFFF |call tsc.00401160 ;空函数
0040245A|.4E |dec esi ;i--
0040245B|.^ 0F89 5CFFFFFF \jns tsc.004023BD
00402461|>8BB424 D0040000mov esi,dword ptr ss: ;传入的地址参数,保存返回值
00402468|.8D9424 D8000000lea edx,dword ptr ss: ;edx ==> "597922","10902440",第1个余数的最后值,这里的与前面的为同一地址
0040246F|.52 push edx ;char * src
00402470|.8BCE mov ecx,esi ;char * dst, ecx保存地址dst,指向第1个余数的最后值
00402472|.E8 B9ECFFFF call tsc.00401130 ;__strmove(char *dst, char *src)
00402477|.C74424 0C 010000>mov dword ptr ss:,0x1
0040247F|.8D4C24 10 lea ecx,dword ptr ss: ;ecx==>"5789538","9507057",第2个余数的最后值
00402483|.C68424 C8040000 >mov byte ptr ss:,0x3
0040248B|.E8 D0ECFFFF call tsc.00401160 ;空函数
00402490|.8D8C24 F8030000lea ecx,dword ptr ss: ;ecx==>"2"
00402497|.C68424 C8040000 >mov byte ptr ss:,0x2
0040249F|.E8 BCECFFFF call tsc.00401160 ;空函数
004024A4|.8D8C24 D8000000lea ecx,dword ptr ss: ;ecx==>"597922","10902440",第1个余数的最后值
004024AB|.C68424 C8040000 >mov byte ptr ss:,0x1
004024B3|.E8 A8ECFFFF call tsc.00401160 ;空函数
004024B8|.8D8C24 A0010000lea ecx,dword ptr ss: ;ecx==>"10011010101101"
004024BF|.C68424 C8040000 >mov byte ptr ss:,0x0
004024C7|.E8 94ECFFFF call tsc.00401160 ;空函数
004024CC|.8B8C24 C0040000mov ecx,dword ptr ss: ;保存的SEH指针
004024D3|.8BC6 mov eax,esi ;返回地址,eax==>"597922","10902440",第1个余数的最后值
004024D5|.5F pop edi ;ntdll.777EEEDD
004024D6|.5E pop esi ;ntdll.777EEEDD
004024D7|.64:890D 00000000 mov dword ptr fs:,ecx ;恢复 SEH
004024DE|.5B pop ebx ;ntdll.777EEEDD
004024DF|.81C4 C0040000 add esp,0x4C0
004024E5\.C2 0C00 retn 0xC
004024E8 90 nop
上面最后一层代码分析,也都写在注释中了,其中两个数学库例程调用是关键,而其功能,是在几遍循环后,猜出来的,哈哈哈。。。。不信,你可以跟进去看看,绝对会绕晕去。。。。
这两个过程就是 call tsc.00401770 (大数乘法)和 call tsc.00401EB0 (大数求模),多用几次Windows的计算器就可猜出来了。
这个crackme的整个序列号分析就完成了,根据RSA算法,没有私钥就只有爆破了,而这个crackme也説了,其只是RSA-24,NOT strong,明显就是要你爆破了。
爆破代码如下,C++的,在 dev-c++中调试通过:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
void getRegkey(char *);
void checkRegkey(char *);
unsigned int multiply_mod(unsigned int, int, unsigned int);
int main(int argc, char** argv) {
printf("calculating regcode, please wait...\n");
char regname[] = "solly";
//char regcode[] = "71676223196885";/// 最佳注册码,两部分都是对的
////
char regcode[] = "000000000000000";/// 填坑占位,加上null,16字节
getRegkey(regcode);
checkRegkey(regcode);
//system("pause");
return 0;
}
void checkRegkey(char * regcode) {
int len = strlen(regcode);
if(len != 14) {
printf("length of regcode '%s' is short.\n", regcode);
return ;
}
char reg1, reg2;
strncpy(reg1, regcode, 7);
reg1 = '\0';
strncpy(reg2, ®code, 7);
reg2 = '\0';
unsigned int regcode1 = (unsigned int)atoi(reg1);
unsigned int regcode2 = (unsigned int)atoi(reg2);
////////
int bit = 9901;
unsigned int divisor = 12790891;
unsigned int checkcode1 = multiply_mod(regcode1, bit, divisor);
unsigned int checkcode2 = multiply_mod(regcode2, bit, divisor);
//////
if((checkcode1 == 8483678) || (checkcode2 == 5666933)) {
printf("regcode '%s' is valid.\n", regcode);
}
}
void getRegkey(char * regcode) {
int bit = 9901;
unsigned int divisor = 12790891;
regcode = '\0';
//////
////// 注册码前后两部分,只要求一个是对的即可,因此另一半可用7位随机数填充
srand((int)time(0));
unsigned int randcode = ((unsigned int)(8999999*rand()))/(RAND_MAX+1) + 1000000;
//// 爆破 RSA-24, 按照crackme的要求,最多9000000次尝试
for(int i=1000000; i<9999999; i++) {
unsigned int checkcode = multiply_mod((unsigned int)i, bit, divisor);
//////
if(checkcode == 8483678) {
printf("regcode1 = %07d%07lu\n", i, randcode);
sprintf(regcode, "%07d%07lu", i, randcode);
break;
}
if(checkcode == 5666933) {
printf("regcode2 = %07lu%07d\n", randcode, i);
sprintf(regcode, "%07lu%07d", randcode, i);
break;
}
}
}
unsigned int multiply_mod(unsigned int regcode, int bit, unsigned int divisor) {
unsigned int mask = 0x80000000;
int bitlen = 32; ///32 bits
do {//// 计算循环次数
if(bit & mask) {
break;
}
mask >>=1; //// 从高位往低位检查,遇到bit为1就停止, 当前及剩下的bit总位数为循环次数
} while((--bitlen)>0);
mask = 0x00000001;
unsigned long long remainder1 = 1;
unsigned long long remainder2 = regcode;
//unsigned long long divisor1 = divisor;
for(int i=0; i<bitlen; i++) {//// 按前面计算的循环次数,进行运算
if(bit & mask) {/// 如果bit为1,则多进行一次乘法并求余运算
remainder1 = (remainder2 * remainder1) % divisor;
}
mask <<=1; /// 下一位bit, 从低位往高位检测bit是否为1
remainder2 = (remainder2 * remainder2) % divisor; /// 平方值求余
}
return remainder1;
}
最后算出来的注册成功的界面如下:
以及最佳序列号,就是前后两部分都可以通过验证:
完毕!!!!!!
_默默_ 发表于 2019-4-11 17:09
大佬都不用ida的吗还是说做逆向最好不用ida
IDA主要用来静态分析,查一些各类编程语言的内置函数名,动态分析,我觉得太庞大了点。 kilkilo502 发表于 2019-4-11 21:12
一个CM写的算法比价值1000的软件还复杂。。。
还可以写的更复杂点。多循环几遍。。弄的让人有 ...
CM的主责就是干这个的呀 感谢分享 谢谢楼主的分享。 技术帖,牛! 谢谢楼主的分享。{:1_893:}
谢谢楼主的分享。 老哥我刚准备发81,就看见你发了,也太巧了吧。 大佬都不用ida的吗还是说做逆向最好不用ida 大神无处不在