简单字符串加解密函数提取
kongfoo/2008.4.14

网上下载的中图法分类号查询普及版,delphi程序,数据库用mdb,
数据加密了。目的是要使用该数据库。窗体有个TreeView,显示类目,
点击就显示详细资料。DeDe看TreeView1Change找到SQL语句,生成加
密后的关键字在数据库中搜索,计算函数在4bb3c8:

计算函数:
004D071A    E8 A9ACFEFF     CALL ztf_ztc.004BB3C8

函数代码:

代码:
***** TRY
|
004BB3EB   64FF30                 push    dword ptr fs:[eax]
004BB3EE   648920                 mov     fs:[eax], esp

* Reference to: System.Randomize;
|
004BB3F1   E89677F4FF             call    00402B8C
004BB3F6   C745F802000000         mov     dword ptr [ebp-$08], $00000002
004BB3FD   8B45FC                 mov     eax, [ebp-$04]

* Reference to: System.@LStrLen(String):Integer;
|
004BB400   E8C795F4FF             call    004049CC
004BB405   8BD0                   mov     edx, eax
004BB407   03D2                   add     edx, edx
004BB409   42                     inc     edx
004BB40A   8BC7                   mov     eax, edi

* Reference to: System.@LStrSetLength;
|
004BB40C   E83F99F4FF             call    00404D50
004BB411   8B45FC                 mov     eax, [ebp-$04]

* Reference to: System.@LStrLen(String):Integer;
|
004BB414   E8B395F4FF             call    004049CC
004BB419   48                     dec     eax
004BB41A   85C0                   test    eax, eax
004BB41C   7C63                   jl      004BB481
004BB41E   40                     inc     eax
004BB41F   8945F4                 mov     [ebp-$0C], eax
004BB422   33DB                   xor     ebx, ebx
004BB424   8B45FC                 mov     eax, [ebp-$04]
004BB427   0FB63418               movzx   esi, byte ptr [eax+ebx]  ==取出字符
004BB42B   8BC7                   mov     eax, edi

* Reference to: crtl.__pure_error_;
|           or: crtl.__matherrl;
|           or: crtl._gcvt;
|           or: System.FPower10;
|           or: System.UniqueString(String;String);overload;
|           or: System.@UniqueStringA(String;String);
|
004BB42D   E8EA97F4FF             call    00404C1C
004BB432   8BD3                   mov     edx, ebx
004BB434   03D2                   add     edx, edx
004BB436   03C2                   add     eax, edx
004BB438   50                     push    eax
004BB439   8BC6                   mov     eax, esi        ==esi:字符
004BB43B   83E00F                 and     eax, +$0F       ==保留低位
004BB43E   8B55F8                 mov     edx, [ebp-$08]  ==strLength
004BB441   03D2                   add     edx, edx
004BB443   8D14D524464D00         lea     edx, [$4D4624+edx*8]  ==查表

004D4624  61 62 63 64 65 66 68 69 6A 6B 6C 6D 6E 6F 70 71  abcdefhijklmnopq
004D4634  30 31 32 33 34 35 36 37 38 39 61 62 63 64 65 66  0123456789abcdef
004D4644  61 7A 68 6A 6C 3B 2A 38 30 2E 23 28 59 42 4E 4B  azhjl;*80.#(YBNK
004D4654  61 62 63 64 65 66 68 69 6A 6B 6C 6D 6E 6F 70 71  abcdefhijklmnopq
004D4664  61 62 63 64 65 66 68 69 6A 6B 6C 6D 6E 6F 70 71  abcdefhijklmnopq
004D4674  61 62 63 64 65 66 68 69 6A 6B 6C 6D 6E 6F 70 71  abcdefhijklmnopq
004D4684  61 62 63 64 65 66 68 69 6A 6B 6C 6D 6E 6F 70 71  abcdefhijklmnopq
004D4694  61 62 63 64 65 66 68 69 6A 6B 6C 6D 6E 6F 70 71  abcdefhijklmnopq
004D46A4  61 62 63 64 65 66 68 69 6A 6B 6C 6D 6E 6F 70 71  abcdefhijklmnopq
004D46B4  61 62 63 64 65 66 68 69 6A 6B 6C 6D 6E 6F 70 71  abcdefhijklmnopq


004BB44A   8A0402                 mov     al, byte ptr [edx+eax]  ==从表中取出值
004BB44D   5A                     pop     edx
004BB44E   8802                   mov     [edx], al ==保存该值
004BB450   8BC7                   mov     eax, edi

* Reference to: crtl.__pure_error_;
|           or: crtl.__matherrl;
|           or: crtl._gcvt;
|           or: System.FPower10;
|           or: System.UniqueString(String;String);overload;
|           or: System.@UniqueStringA(String;String);
|
004BB452   E8C597F4FF             call    00404C1C
004BB457   8BD3                   mov     edx, ebx
004BB459   03D2                   add     edx, edx
004BB45B   8D441001               lea     eax, [eax+edx+$01]
004BB45F   50                     push    eax
004BB460   81E6F0000000           and     esi, $000000F0  ==字符值,保留高位
004BB466   C1EE04                 shr     esi, $04        ==放到低位,重复做一下上面的查表操作
004BB469   8B45F8                 mov     eax, [ebp-$08]
004BB46C   03C0                   add     eax, eax
004BB46E   8D04C524464D00         lea     eax, [$4D4624+eax*8]
004BB475   8A0430                 mov     al, byte ptr [eax+esi]
004BB478   5A                     pop     edx
004BB479   8802                   mov     [edx], al
004BB47B   43                     inc     ebx
004BB47C   FF4DF4                 dec     dword ptr [ebp-$0C]
004BB47F   75A3                   jnz     004BB424
004BB481   8D55F0                 lea     edx, [ebp-$10]
004BB484   8B45F8                 mov     eax, [ebp-$08]

* Reference to: SysUtils.IntToStr(Integer):AnsiString;overload;
|
004BB487   E8F8DBF4FF             call    00409084
004BB48C   8B45F0                 mov     eax, [ebp-$10]
004BB48F   8A18                   mov     bl, byte ptr [eax]
004BB491   8B45FC                 mov     eax, [ebp-$04]

* Reference to: System.@LStrLen(String):Integer;
|           or: System.@DynArrayLength;
|           or: System.DynArraySize(Pointer):Integer;
|           or: Variants.DynArraySize(Pointer):Integer;
|
004BB494   E83395F4FF             call    004049CC
004BB499   8BF0                   mov     esi, eax
004BB49B   03F6                   add     esi, esi
004BB49D   8BC7                   mov     eax, edi

* Reference to: crtl.__pure_error_;
|           or: crtl.__matherrl;
|           or: crtl._gcvt;
|           or: System.FPower10;
|           or: System.UniqueString(String;String);overload;
|           or: System.@UniqueStringA(String;String);
|
004BB49F   E87897F4FF             call    00404C1C
004BB4A4   881C30                 mov     [eax+esi], bl
004BB4A7   33C0                   xor     eax, eax
004BB4A9   5A                     pop     edx
004BB4AA   59                     pop     ecx
004BB4AB   59                     pop     ecx
004BB4AC   648910                 mov     fs:[eax], edx

****** FINALLY
先将上面的代码逆向成delphi代码:
代码:
procedure TForm1.Button1Click(Sender: TObject);
var s,OutPutStr:String;
    strLen,i,Index:Integer;
    aChar:Char;
begin
  s:=Edit1.Text;
  strLen:=Length(s);
  OutPutStr:='';
  for i:=1 to strLen do
  begin
    aChar:=s[i];
    Index:=Ord(aChar) and $F;
    OutPutStr:=OutPutStr+keyArray[strLen*2*8+Index];
    Index:=Ord(aChar) and $F0 shr 4;
    OutPutStr:=OutPutStr+keyArray[strLen*2*8+Index];
  end;
  Edit2.Text:=OutPutStr;
end;
上面的代码是点击TreeView事件中用到的计算代码,用于根据分类号进行查询,
函数内用到的表长度为160字节,查表过程中字符串长度会乘以16,即如果字串
长度大于10将导致查表越界。所以这里是假设分类号不超10个字符。但数据库
内其他字段的数据很多是很长的,哪么应该还有其它函数进行解密并在软件界面
中显示的操作。

FormCreate中看看:

解密函数:
004D0B0A   E8C5A9FEFF             call    004BB4D4

跟踪到解密字串过程:
代码:
004BB51A    8B45 FC         MOV EAX,DWORD PTR SS:[EBP-4]  ==字串
004BB51D    E8 AA94F4FF     CALL ztf_ztc.004049CC         ==Length
004BB522    8B55 FC         MOV EDX,DWORD PTR SS:[EBP-4]  ==字串
004BB525    8A5402 FF       MOV DL,BYTE PTR DS:[EDX+EAX-1]  ==字串尾字符
004BB529    8D45 F4         LEA EAX,DWORD PTR SS:[EBP-C]
004BB52C    E8 C393F4FF     CALL ztf_ztc.004048F4         ==StrFromChar
004BB531    8B45 F4         MOV EAX,DWORD PTR SS:[EBP-C]
004BB534    E8 AFDBF4FF     CALL ztf_ztc.004090E8         ==StrToInt,最后一位是数字,通常是2
004BB539    8BF8            MOV EDI,EAX
004BB53B    8B45 FC         MOV EAX,DWORD PTR SS:[EBP-4]  ==字串
004BB53E    E8 8994F4FF     CALL ztf_ztc.004049CC         ==Length
004BB543    8BD0            MOV EDX,EAX
004BB545    D1FA            SAR EDX,1
004BB547    79 03           JNS SHORT ztf_ztc.004BB54C

004BB54C    8B45 F8         MOV EAX,DWORD PTR SS:[EBP-8]
004BB54F    E8 FC97F4FF     CALL ztf_ztc.00404D50       ==SetLength
004BB554    BE 01000000     MOV ESI,1
004BB559    8B45 FC         MOV EAX,DWORD PTR SS:[EBP-4]  ==字串
004BB55C    E8 6B94F4FF     CALL ztf_ztc.004049CC    ==Length
004BB561    3D FA000000     CMP EAX,0FA
004BB566    7E 50           JLE SHORT ztf_ztc.004BB5B8

004BB572    8B45 FC         MOV EAX,DWORD PTR SS:[EBP-4]  ==字串
004BB575    8A5430 FF       MOV DL,BYTE PTR DS:[EAX+ESI-1] ==从头开始取字符
004BB579    8BC7            MOV EAX,EDI                    ==edi就是字串末位的数字,2
004BB57B    E8 24FEFFFF     CALL ztf_ztc.004BB3A4

004BB3A4    53              PUSH EBX
004BB3A5    56              PUSH ESI
004BB3A6    BE 0F000000     MOV ESI,0F
004BB3AB    B9 33464D00     MOV ECX,ztf_ztc.004D4633                 ; ASCII 71,"0123456789abcdefazhjl;*80.#(YBNKabcdefhijklmnopqabcdefhijklmnopqabcdefhijklmnopqabcdefhijklmnopqabcdefhijklmnopqa"
004BB3B0    8BD8            MOV EBX,EAX  ==4d4633就是上面的表开头4d4624+0F,这里eax就是字串末位数值
004BB3B2    03DB            ADD EBX,EBX
004BB3B4    3A14D9          CMP DL,BYTE PTR DS:[ECX+EBX*8]  ==DL:要解密的字符
004BB3B7    74 07           JE SHORT ztf_ztc.004BB3C0  ==在表中向表头方向查找,找到即返回下标值
004BB3B9    4E              DEC ESI
004BB3BA    49              DEC ECX
004BB3BB    83FE FF         CMP ESI,-1
004BB3BE  ^ 75 F0           JNZ SHORT ztf_ztc.004BB3B0
004BB3C0    8BC6            MOV EAX,ESI
004BB3C2    5E              POP ESI
004BB3C3    5B              POP EBX
004BB3C4    C3              RETN

004BB580    33DB            XOR EBX,EBX
004BB582    83F8 FF         CMP EAX,-1
004BB585    74 02           JE SHORT ztf_ztc.004BB589
004BB587    8BD8            MOV EBX,EAX
004BB589    8B45 FC         MOV EAX,DWORD PTR SS:[EBP-4]  ==字串
004BB58C    8A1430          MOV DL,BYTE PTR DS:[EAX+ESI]  ==第2个字符
004BB58F    8BC7            MOV EAX,EDI
004BB591    E8 0EFEFFFF     CALL ztf_ztc.004BB3A4  ==解密
004BB596    83F8 FF         CMP EAX,-1
004BB599    74 05           JE SHORT ztf_ztc.004BB5A0
004BB59B    C1E0 04         SHL EAX,4
004BB59E    02D8            ADD BL,AL
004BB5A0    83C6 02         ADD ESI,2        ==两值组合
004BB5A3    8B45 F8         MOV EAX,DWORD PTR SS:[EBP-8]
004BB5A6    E8 7196F4FF     CALL ztf_ztc.00404C1C
004BB5AB    8BD6            MOV EDX,ESI
004BB5AD    D1FA            SAR EDX,1
004BB5AF    79 03           JNS SHORT ztf_ztc.004BB5B4
004BB5B1    83D2 00         ADC EDX,0
004BB5B4    885C10 FF       MOV BYTE PTR DS:[EAX+EDX-1],BL  ==结果写入字串
004BB5B8    8B45 FC         MOV EAX,DWORD PTR SS:[EBP-4]
004BB5BB    E8 0C94F4FF     CALL ztf_ztc.004049CC
004BB5C0    3BF0            CMP ESI,EAX
004BB5C2  ^ 7C AE           JL SHORT ztf_ztc.004BB572
将上面解密字串函数转成delphi:
代码:
function DecryptChar(aChar:Char;magicValue:Integer):Integer;
var i,Index,offset:Integer;
begin
  Index:=$F;
  offset:=0;
  for i:=0 to $F do
  begin
    if keyArray[magicValue*2*8+$F+offset]=aChar then
    begin
      result:=Index;
      break;
    end;
    Dec(Index);
    Dec(offset);
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var s,OutPutStr:String;
    magicValue,strLen,i,result1,result2:Integer;
begin
  s:=Edit4.Text;
  strLen:=Length(s);
  if strLen>$FA then Exit;
  magicValue:=StrToInt(Copy(s,strLen,1));
  i:=1;
  OutPutStr:='';
  while i<strLen-1 do
  begin
    result1:=DecryptChar(s[i],magicValue);
    result2:=DecryptChar(s[i+1],magicValue);
    OutPutStr:=OutPutStr+Chr(result2 shl 4 + result1);
    Inc(i,2);
  end;
  Edit3.Text:=OutPutStr;
end;
解密代码成功将
;(#BhB((l(NY#YaYB(8Nl(jK;BB(8Ya(#(jKj#0#zj0j*j8jBhzj.jzj8jlY#Nj#.#2
解密成
第一次世界大战前后(1867-1917年)

有了解密代码的研究,再回头看看之前生成密文的代码,修改一下就
变成加密代码了:
代码:
procedure TForm1.Button1Click(Sender: TObject);
var s,OutPutStr:String;
    strLen,i,Index,magicValue:Integer;
    aChar:Char;
begin
  s:=Edit1.Text;
  strLen:=Length(s);
  magicValue:=2;
  OutPutStr:='';
  for i:=1 to strLen do
  begin
    aChar:=s[i];
    Index:=Ord(aChar) and $F;
    OutPutStr:=OutPutStr+keyArray[magicValue*2*8+Index];
    Index:=Ord(aChar) and $F0 shr 4;
    OutPutStr:=OutPutStr+keyArray[magicValue*2*8+Index];
  end;
  OutPutStr:=OutPutStr+IntToStr(magicValue);
  Edit2.Text:=OutPutStr;
end;
根据解密函数对keyArray的使用方法,代码中修改了keyArray下标
strLen*2*8+Index->magicValue*2*8+Index。

这样就得到了一套字串加解密函数了。为了保留对汇编代码的直观性,
代码没有进行优化。接下来是解密字串写回数据库,这部分略。