【软件名称】 xx外挂浮点注册算法分析
【文章作者】 仙剑太郎
【网站地址】 中国X黑客小组 www.CnXHacker.com
【破解工具】 Ollydbg
【软件语言】 Microsoft Visual C++
【破解声明】 我是一只小菜鸟,偶的一点心得,愿与大家分享:)
-------------------------------------------------------------
【破解过程】

一个冒险岛的小外挂,无壳,VC写的,偶们菜鸟拿来做练习最好不过了~
软件使用了浮点运算,正好复习一下浮点指令:)好,下面开工了..
我这里的机器码是DFMPHHCCF4,随便输入注册码,根据出错提示很容易找到关键地方,向上翻,会找到算法开始的地方,如下

004028B9   .  33C0  xor eax,eax
004028BB   .  8B35 >mov esi,dword ptr ds:[41F54C]         ;  038版本?0041F560
004028C1   .  89442>mov dword ptr ss:[esp+2D],eax
004028C5   .  33D2  xor edx,edx
004028C7   .  89442>mov dword ptr ss:[esp+31],eax
004028CB   .  89542>mov dword ptr ss:[esp+44],edx
004028CF   .  89442>mov dword ptr ss:[esp+35],eax
004028D3   .  88542>mov byte ptr ss:[esp+2C],dl
004028D7   .  66:89>mov word ptr ss:[esp+39],ax
004028DC   .  89742>mov dword ptr ss:[esp+10],esi
004028E0   .  88442>mov byte ptr ss:[esp+3B],al
004028E4   .  8B7C2>mov edi,dword ptr ss:[esp+18]         ;  机器码指针放进edi
004028E8   .  83C9 >or ecx,FFFFFFFF
004028EB   .  33ED  xor ebp,ebp
004028ED   .  C6442>mov byte ptr ss:[esp+44],1
004028F2   .  F2:AE repne scas byte ptr es:[edi]          ;  计算机器码长度
004028F4   .  F7D1  not ecx
004028F6   .  49    dec ecx                               ;  长度为0xA
004028F7   .  89542>mov dword ptr ss:[esp+14],edx
004028FB   .  8BF9  mov edi,ecx                           ;  保存机器码长度
004028FD   .  0F84 >je 038版本?00402A9E
00402903   .  BE 01>mov esi,1
00402908   .  3BFE  cmp edi,esi                           ;  机器码长度是否小于1?
0040290A   .  89742>mov dword ptr ss:[esp+1C],esi
0040290E   .  0F8C >jl 038版本?004029C3                   ;  小于则over
00402914   .  DD05 >fld qword ptr ds:[41A768]             ;  将0.0放入st0
0040291A   >  8B4C2>mov ecx,dword ptr ss:[esp+18]         ;  循环的开始,机器码指针保存到ecx中
0040291E   .  DB442>fild dword ptr ss:[esp+1C]            ;  第N位放到st0
00402922   .  8A5C3>mov bl,byte ptr ds:[ecx+esi-1]        ;  机器码逐字符ASCII放到bl
00402926   .  0FBED>movsx edx,bl                          ;  保存到edx中
00402929   .  DD5C2>fstp qword ptr ss:[esp+24]            ;  取st0值到esp+0x24中,出栈
0040292D   .  89542>mov dword ptr ss:[esp+1C],edx         ;  将第N个ASCII放到esp+0x1c
00402931   .  DB442>fild dword ptr ss:[esp+1C]            ;  第N个ASCII整数值到st0中
00402935   .  0FBEC>movsx eax,bl                          ;  第N个ASCII数值保存到eax
00402938   .  D9FA  fsqrt                                 ;  取st0第N个ASCII平方根
0040293A   .  0FAFC>imul eax,esi                          ;  第N个ASCII*N
0040293D   .  DC4C2>fmul qword ptr ss:[esp+24]            ;  st0*N=st0
00402941   .  DC05 >fadd qword ptr ds:[41A760]            ;  平方根的值+1
00402947   .  0FAFC>imul eax,esi                          ;  已经*N的ASCII再*N
0040294A   .  89442>mov dword ptr ss:[esp+1C],eax         ;  *完的结果放到esp+0x1c里
0040294E   .  DB442>fild dword ptr ss:[esp+1C]            ;  *完的结果到st0中,以十进制表示
00402952   .  DEC9  fmulp st(1),st                        ;  *完的结果再*st1,即st0=st0*st1
00402954   .  D8C1  fadd st,st(1)                         ;  st0+st1
00402956   .  E8 75>call 038版本?00403CD0                 ;  将st0取整,st0出栈
0040295B   .  99    cdq
0040295C   .  DDD8  fstp st                               ;  出栈一次
0040295E   .  B9 A0>mov ecx,186A0                         ;  常数100000
00402963   .  F7F9  idiv ecx                              ;  取整后的数余100000
00402965   .  89542>mov dword ptr ss:[esp+14],edx         ;  余数为密钥1,Key1★
00402969   .  0FBED>movsx edx,bl                          ;  第N位ASCII放到ebx
0040296C   .  89542>mov dword ptr ss:[esp+1C],edx         ;  第N位ASCII放到esp+1c
00402970   .  DB442>fild dword ptr ss:[esp+1C]            ;  第N位ASCII放到存进st0,十进制表示
00402974   .  DD05 >fld qword ptr ds:[41A758]             ;  将2.00放到st0,作为底数
0040297A   .  E8 31>call 038版本?00403AB0                 ;  求对数LOG等运算的CALL,跟进

================================
0040297A此CALL用来求对数等其它运算,F7跟进看看,来到下面:
================================

00403AB0  /$  83EC >sub esp,10
00403AB3  |.  D9C9  fxch st(1)                            ;  st0和st1互换
00403AB5  |.  DD1C2>fstp qword ptr ss:[esp]               ;  st0出栈
00403AB8  |.  DD542>fst qword ptr ss:[esp+8]              ;  将浮点2.00以实数形式放到esp+8,值为0
00403ABC  |.  8B442>mov eax,dword ptr ss:[esp+C]          ;  将40000000放eax
00403AC0  |.  E8 0D>call 038版本?00403AD2                 ;  求LOG的CALL,跟进
00403AC5  |.  83C4 >add esp,10
00403AC8  \.  C3    retn

================================
再跟进一下00403AC0,来到下面:
================================

00403AD2   $  8BC8  mov ecx,eax
00403AD4   .  50    push eax
00403AD5   .  9B    wait
00403AD6   .  D93C2>fstcw word ptr ss:[esp]
00403AD9   .  66:81>cmp word ptr ss:[esp],27F
00403ADF   .  74 05 je short 038版本?00403AE6
00403AE1   .  E8 4F>call 038版本?00406935
00403AE6   >  81E1 >and ecx,7FF00000                      ;  40000000 and 7FF00000
00403AEC   .  8D542>lea edx,dword ptr ss:[esp+8]
00403AF0   .  81F9 >cmp ecx,7FF00000
00403AF6   .  0F84 >je 038版本?00403B99
00403AFC   .  E8 64>call 038版本?00406965
00403B01   .  0F84 >je 038版本?00403B95
00403B07   .  A9 00>test eax,7FF00000
00403B0C   .  0F84 >je 038版本?00403C08
00403B12   >  8A4C2>mov cl,byte ptr ss:[esp+F]            ;  40?
00403B16   .  80E1 >and cl,80                             ;  40 and 80=0
00403B19   .  0F85 >jnz 038版本?00403C80
00403B1F   >  D9F1  fyl2x                                 ;  st1 * log2(st0)
00403B21   .  E8 FA>call 038版本?00406920                 ;  四舍五入等运算,跟进

================================
00403B21的CALL也是关键的计算,F7跟进,来到下面:
================================

00406920  /$  D9C0  fld st
00406922  |.  D9FC  frndint                               ;  四舍五入
00406924  |.  DCE1  fsubr st(1),st                        ;  st1-st0,负值
00406926  |.  D9C9  fxch st(1)                            ;  结果交换
00406928  |.  D9E0  fchs                                  ;  求负数,前面加-
0040692A  |.  D9F0  f2xm1                                 ;  st(0) <- (2 ^ st(0)) - 1
0040692C  |.  D9E8  fld1                                  ;  1放入st0
0040692E  |.  DEC1  faddp st(1),st                        ;  相加然后出栈
00406930  |.  D9FD  fscale                                ;  2 ^ st1*st(0)
00406932  |.  DDD9  fstp st(1)                            ;  出栈
00406934  \.  C3    retn

================================
上面CALL的运算流程一目了然,我们retn回去,中途的很多东西都是编译器生成的容错代码,与注册码算法无关,所以中间有些东西不用理会
继续返回到这里:
================================

0040297F   .  DC4C2>fmul qword ptr ss:[esp+24]            ;  求对数的结果*N
00402983   .  E8 48>call 038版本?00403CD0                 ;  将st0取整
00402988   .  DB442>fild dword ptr ss:[esp+14]            ;  将余数放st0
0040298C   .  8BCE  mov ecx,esi                           ;  esi=N
0040298E   .  0FAFC>imul ecx,ebp                          ;  ebp为新变量*N
00402991   .  D9C0  fld st                                ;  入栈st1
00402993   .  D9FA  fsqrt                                 ;  *完的结果开平方
00402995   .  03C1  add eax,ecx                           ;  开平方的结果与*的结果相加
00402997   .  B9 A0>mov ecx,186A0                         ;  常数10000
0040299C   .  99    cdq
0040299D   .  F7F9  idiv ecx                              ;  相加后的数余100000
0040299F   .  8BEA  mov ebp,edx                           ;  保存结果为密钥2,Key2★
004029A1   .  E8 2A>call 038版本?00403CD0                 ;  取整
004029A6   .  03C5  add eax,ebp                           ;  取整结果与相加结果再加
004029A8   .  B9 A0>mov ecx,186A0                         ;  常数10000
004029AD   .  99    cdq
004029AE   .  F7F9  idiv ecx                              ;  余数为密钥3,Key3★
004029B0   .  46    inc esi                               ;  esi+1
004029B1   .  3BF7  cmp esi,edi                           ;  完了没?
004029B3   .  89742>mov dword ptr ss:[esp+1C],esi         ;  esi放到esp+1c保存
004029B7   .^ 0F8E >jle 038版本?0040291A                  ;  未完跳回去,完了则向下

================================
这里根据机器码算出来的三个注册密钥已经生成完毕,我的机器码对应的三个密钥是:
Key1=0x11F73  ,  Key2=0x18088  ,  Key3=18197

下面还会跟据三个密钥生成一个15 byte的表:
================================

004029BD   .  8B5C2>mov ebx,dword ptr ss:[esp+20]         ;  密钥生成完毕,到表的生成
004029C1   .  DDD8  fstp st                               ;  st0出栈
004029C3   >  33C0  xor eax,eax                           ;  清零,第一段表开始
004029C5   >  8BC8  mov ecx,eax                           ;  前5位表,累加值存入ecx
004029C7   .  8B742>mov esi,dword ptr ss:[esp+14]         ;  取浮点运算结果HEX放到esi中
004029CB   .  0FAFC>imul ecx,eax                          ;  循环相乘
004029CE   .  0FAFC>imul ecx,eax                          ;  再乘一次
004029D1   .  8D4C3>lea ecx,dword ptr ds:[ecx+esi+1F]     ;  esi=Key1,累加+0x1F
004029D5   .  81E1 >and ecx,8000007F                      ;  与运算and 0x800007F
004029DB   .  79 05 jns short 038版本?004029E2            ;  跳了
004029DD   .  49    dec ecx
004029DE   .  83C9 >or ecx,FFFFFF80
004029E1   .  41    inc ecx
004029E2   >  884C0>mov byte ptr ss:[esp+eax+2C],cl       ;  保存到表的第一段中★
004029E6   .  40    inc eax                               ;  eax递增
004029E7   .  83F8 >cmp eax,5                             ;  少于5?
004029EA   .^ 7C D9 jl short 038版本?004029C5             ;  小于5则继续循环
004029EC   .  B8 05>mov eax,5                             ;  第二段表开始
004029F1   >  8BC8  mov ecx,eax                           ;  以下同上
004029F3   .  0FAFC>imul ecx,eax
004029F6   .  0FAFC>imul ecx,eax
004029F9   .  8D4C2>lea ecx,dword ptr ds:[ecx+ebp+1F]     ;  ebp=Key2,其余同上
004029FD   .  81E1 >and ecx,8000007F
00402A03   .  79 05 jns short 038版本?00402A0A            ;  跳了
00402A05   .  49    dec ecx
00402A06   .  83C9 >or ecx,FFFFFF80
00402A09   .  41    inc ecx
00402A0A   >  884C0>mov byte ptr ss:[esp+eax+2C],cl       ;  保存到表的第二段中★
00402A0E   .  40    inc eax
00402A0F   .  83F8 >cmp eax,0A                            ;  到10了没?
00402A12   .^ 7C DD jl short 038版本?004029F1             ;  未完继续
00402A14   .  B8 0A>mov eax,0A                            ;  第三段表开始
00402A19   >  8BC8  mov ecx,eax                           ;  以下同上
00402A1B   .  0FAFC>imul ecx,eax
00402A1E   .  0FAFC>imul ecx,eax
00402A21   .  8D4C1>lea ecx,dword ptr ds:[ecx+edx+1F]     ;  edx=Key3,其余同上
00402A25   .  81E1 >and ecx,8000007F
00402A2B   .  79 05 jns short 038版本?00402A32            ;  跳了
00402A2D   .  49    dec ecx
00402A2E   .  83C9 >or ecx,FFFFFF80
00402A31   .  41    inc ecx
00402A32   >  884C0>mov byte ptr ss:[esp+eax+2C],cl       ;  保存到表的第三段中★
00402A36   .  40    inc eax
00402A37   .  83F8 >cmp eax,0F                            ;  到15了没?
00402A3A   .^ 7C DD jl short 038版本?00402A19             ;  未完继续

================================
上面分三次生成了一个15 byte的表,我这里表的数据如下
0012F56C  12 13 1A 2D 52 24 7F 7E 27 00 1E 69 76 4B 6E     -R$~'.ivKn
即:
表1
0x12 0x13 0x1A 0x2D 0x52
表2
0x24 0x7F 0x7E 0x27 0x00 
表3
0x1E 0x69 0x76 0x4B 0x6E
================================

00402A3C   .  33F6  xor esi,esi                           ;  表已生成完毕,开始计算注册码
00402A3E   .  C6442>mov byte ptr ss:[esp+3B],0
00402A43   .  33C9  xor ecx,ecx
00402A45   >  8A443>mov al,byte ptr ss:[esp+esi+2C]       ;  取表中第N位的低8位到al
00402A49   .  3C 30 cmp al,30                             ;  '0'
00402A4B   .  7C 04 jl short 038版本?00402A51
00402A4D   .  3C 39 cmp al,39                             ;  '9'
00402A4F   .  7E 29 jle short 038版本?00402A7A            ;  非可显字符则跳
00402A51   >  3C 41 cmp al,41                             ;  'a'
00402A53   .  7C 04 jl short 038版本?00402A59
00402A55   .  3C 5A cmp al,5A                             ;  'z'
00402A57   .  7E 21 jle short 038版本?00402A7A            ;  非可显字符则跳
00402A59   >  3C 61 cmp al,61                             ;  'A'
00402A5B   .  7C 04 jl short 038版本?00402A61
00402A5D   .  3C 7A cmp al,7A                             ;  'Z'
00402A5F   .  7E 19 jle short 038版本?00402A7A            ;  非可显字符则跳
00402A61   >  0FBED>movsx edx,al                          ;  表中第N位放到edx
00402A64   .  8D440>lea eax,dword ptr ds:[edx+ecx+1F]     ;  edx=表第N位ASCII,相加结果放到eax中
00402A68   .  25 7F>and eax,8000007F                      ;  与运算and 0x800007F
00402A6D   .  79 05 jns short 038版本?00402A74            ;  跳了
00402A6F   .  48    dec eax
00402A70   .  83C8 >or eax,FFFFFF80
00402A73   .  40    inc eax
00402A74   >  88443>mov byte ptr ss:[esp+esi+2C],al       ;  保存注册码第N位
00402A78   .^ EB CB jmp short 038版本?00402A45            ;  继续下一个
00402A7A   >  83C1 >add ecx,7                             ;  非可显字符ASCII+0x7
00402A7D   .  46    inc esi                               ;  记数器加1
00402A7E   .  83F9 >cmp ecx,69                            ;  0x7*0x0F=0x69,是否循环完了15次?
00402A81   .^ 7C C2 jl short 038版本?00402A45             ;  未完继续,完了向下

================================
上面这段是根据之前的表生成0-9,a-z,A-Z段的注册码,以便显示出来
到这里已经可以看到注册码的原始状态,我这里显示的注册码原始状态是
0012F56C  31 39 47 61 52 66 48 4E 55 78 68 69 76 4B 6E     19GaRfHNUxhivKn
下面继续看看还做了些什么
================================

00402A83   .  8D4C2>lea ecx,dword ptr ss:[esp+2C]         ;  保存得到的15位注册码指针到ecx
00402A87   .  8D542>lea edx,dword ptr ss:[esp+10]         ;  空指针
00402A8B   .  51    push ecx                              ;  注册码入栈
00402A8C   .  68 C8>push 038版本?0041F0C8                 ;  ASCII "%s"
00402A91   .  52    push edx
00402A92   .  E8 66>call 038版本?00410CFD                 ;  计算注册码长度=0x0F
00402A97   .  8B742>mov esi,dword ptr ss:[esp+1C]         ;  注册码指针保存到esi
00402A9B   .  83C4 >add esp,0C
00402A9E   >  8B46 >mov eax,dword ptr ds:[esi-8]          ;  保存长度
00402AA1   .  33C9  xor ecx,ecx
00402AA3   .  85C0  test eax,eax
00402AA5   .  7E 2D jle short 038版本?00402AD4            ;  小于等于0x0F则over
00402AA7   >  8D79 >lea edi,dword ptr ds:[ecx+1]          ;  记数器
00402AAA   .  BD 06>mov ebp,6
00402AAF   .  8BC7  mov eax,edi
00402AB1   .  99    cdq
00402AB2   .  F7FD  idiv ebp                              ;  记数器余0x6
00402AB4   .  85D2  test edx,edx                          ;  是否0,其实这里是判断注册码第6和第12位
00402AB6   .  75 13 jnz short 038版本?00402ACB            ;  不是则跳
00402AB8   .  68 7C>push 038版本?0041F37C                 ;  是第6位或第12位了
00402ABD   .  51    push ecx
00402ABE   .  8D4C2>lea ecx,dword ptr ss:[esp+18]
00402AC2   .  E8 E3>call 038版本?004108AA                 ;  此CALL是往第6和第12位添加"-"
00402AC7   .  8B742>mov esi,dword ptr ss:[esp+10]
00402ACB   >  8B46 >mov eax,dword ptr ds:[esi-8]          ;  注册码长度
00402ACE   .  8BCF  mov ecx,edi                           ;  记数器保存到ecx
00402AD0   .  3BC8  cmp ecx,eax                           ;  是否完了15次?
00402AD2   .^ 7C D3 jl short 038版本?00402AA7             ;  未完继续

================================
上面这部分是往原始注册码的第6和第12位添加"-"
添加"-"完成后,注册码就出来了,我这里的注册码是

ESP 0012F540
EBP 00000006
ESI 003E6EE8 ASCII "19GaR-fHNUx-hivKn"
EDI 00000011

完整注册码生成完毕,下面开始比较注册码了
================================

00402AD4   >  8B83 >mov eax,dword ptr ds:[ebx+18C]
00402ADA   .  8B48 >mov ecx,dword ptr ds:[eax-8]
00402ADD   .  85C9  test ecx,ecx                          ;  判断注册码长度
00402ADF   .  75 18 jnz short 038版本?00402AF9            ;  非空则继续
00402AE1   .  6A 10 push 10
00402AE3   .  68 70>push 038版本?0041F370
00402AE8   .  68 5C>push 038版本?0041F35C
00402AED   .  8BCB  mov ecx,ebx
00402AEF   .  E8 EB>call 038版本?004145DF                 ;  弹出"注册码不能为空"出错提示
00402AF4   .  E9 20>jmp 038版本?00402D19                  ;  over
00402AF9   >  56    push esi                              ;  注册码指针,这里可以作内存注册机
00402AFA   .  50    push eax
00402AFB   .  E8 A3>call 038版本?004039A3                 ;  判断注册码是否合法CALL
00402B00   .  83C4 >add esp,8
00402B03   .  85C0  test eax,eax
00402B05   .  0F85 >jnz 038版本?00402D11                  ;  暴破点,跳则over
00402B0B   .  8D83 >lea eax,dword ptr ds:[ebx+188]
00402B11   .  68 50>push 038版本?0041F350
00402B16   .  50    push eax
00402B17   .  8D442>lea eax,dword ptr ss:[esp+24]

【最后小结】
到此,整个注册码生成过程已经了解清楚了,可以开始写注册机了.这里用C写了个注册机,根据上面分析的结果来写的,没有经过优化,写得比较乱,见笑了:)
注册机源码如下(TC 2.0调试通过):

【注册机】

#include <stdio.h>
#include <math.h>
#include <string.h>

double Call1(double inDbl)
{
/* 求对数子函数 */
double tmpCallDbl;
unsigned long tmpCallLng;
tmpCallDbl=log(inDbl)/log(2)*2;     /* 以2为底求对数 */
tmpCallLng=tmpCallDbl+0.5;          /* 四舍五入 */
tmpCallDbl-=tmpCallLng;
tmpCallDbl=pow(2,tmpCallDbl-1+1);
tmpCallDbl=pow(2,tmpCallLng)*tmpCallDbl;
return(tmpCallDbl);
}

void main()
{
/**************************************/
/* KeyGen By XJTL   www.CnXHacker.com */
/**************************************/
  double tmpDbl;
  unsigned long tmpLng1,tmpLng2,OldLng1=0,OldLng2=0,Key1,Key2,Key3,ecx;
  int j,k,l,eax,esi=0;
  char n[15],sn[15];
  clrscr();
  printf("\n********** KeyGen For BB_038wg **********\n");
  printf("\n******Powered By XJTL CnXHacker.com******\n\n");
  printf("Pls Input Your Machine Code:");
  gets(n);
  l=strlen(n);
  printf("\nYour Serial Num is : ");
  for(k=1;k<=l;k++)
  {
    tmpDbl=sqrt(n[k-1])*k+1;
    tmpLng1=n[k-1]*k*k;
    tmpDbl*=tmpLng1;
    OldLng1+=tmpDbl;
    Key1=OldLng1%0x186A0;      /* 密钥1 */
    tmpDbl=Call1(n[k-1]);      /* 调用求对数函数 */
    tmpLng1=tmpDbl*k+0.5;      /* 四舍五入 */
    OldLng2*=k;
    tmpDbl=sqrt(Key1);
    tmpLng1+=OldLng2;
    tmpLng2=tmpLng1;           /* 保存临时变量 */

    OldLng2=tmpLng1%0x186A0;
    Key2=OldLng2;              /* 密钥2 */
    tmpLng1=tmpDbl;            /* 保存临时变量 */
    tmpLng2+=tmpLng1;
    tmpLng2=tmpLng2%0x186A0;
    Key3=tmpLng2;              /* 密钥3 */
  }

/* 根据密钥生成三个表,共15 byte */

  for(eax=0;eax<=4;eax++)
  {
    ecx=eax;
    ecx*=eax;
    ecx*=eax;
    ecx+=Key1+0x1F;
    ecx&=0x8000007F;
    sn[eax]=ecx;            /* 生成表第一部分,5 byte */
  }
  for(eax=5;eax<=9;eax++)
  {
    ecx=eax
    ecx*=eax;
    ecx*=eax;
    ecx+=Key2+0x1F;
    ecx&=0x8000007F;
    sn[eax]=ecx;            /* 生成表第二部分,5 byte */
  }
  for(eax=10;eax<=14;eax++)
  {
    ecx=eax;
    ecx*=eax;
    ecx*=eax;
    ecx+=Key3+0x1F;
    ecx&=0x8000007F;
    sn[eax]=ecx;            /* 生成表第三部分,5 byte */
  }
  ecx=0;
  while(esi<=14)
  {
     /* 遍历表输出可显字符 */
    if(sn[esi]>='0'&&sn[esi]<='9'||sn[esi]>='a'&&sn[esi]<='z'||sn[esi]>='A'&&sn[esi]<='Z')
    {
      ecx+=7;
      esi+=1;
    }
    else
    {
      eax=sn[esi]+ecx+0x1F;
      eax&=0x8000007F;
      sn[esi]=eax;       /* 生成注册码,15 byte */
    }
   }
 /* 注册码添加"-"然后输出 */
 for(j=0;j<=4;j++)
    printf("%c",sn[j]);
 printf("-");
 for(j=5;j<=9;j++)
    printf("%c",sn[j]); 
 printf("-");
 for(j=10;j<=14;j++)
    printf("%c",sn[j]);
 printf("\n\n");
 getch();
}
----------------------------------------------------------------
【版权声明】 本文纯属技术交流, 转载请注明作者并保持文章的完整, 谢谢!