【文章标题】: 对delphi中ImageEn组件的注册分析
【文章作者】: sunsjw
【下载地址】: http://www.hicomponents.com/downloads/232/DImageEn2007.msi
【加壳方式】: 无
【编写语言】: delphi
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
这是一个Delphi图像处理的组件,功能强大,用这个可以开发简单的图像处理软件。
我下载的是FOR delphi2007版本的,其它版本的算法就不知道了。
开始:
注程序在安装目录里的setup.exe,先用peid查,无壳。既然是delphi的东西就要dede出场了。
用dede分析找到注册按钮的事件,反汇编如下
* Reference to control Edit2 : TEdit
|
00447A9A 8B80DC020000 mov eax, [eax+$02DC]
* Reference to: controls.TControl.GetText(TControl):TCaption;
|
00447AA0 E8C3E2FDFF call 00425D68
00447AA5 837DE000 cmp dword ptr [ebp-$20], +$00
00447AA9 746D jz 00447B18
00447AAB 6A12 push $12
00447AAD 6873010000 push $00000173
00447AB2 8D55DC lea edx, [ebp-$24]
00447AB5 8B45FC mov eax, [ebp-$04]
* Reference to control Edit2 : TEdit
|
00447AB8 8B80DC020000 mov eax, [eax+$02DC]
* Reference to: controls.TControl.GetText(TControl):TCaption;
|
00447ABE E8A5E2FDFF call 00425D68
00447AC3 8B45DC mov eax, [ebp-$24]
00447AC6 8D55E5 lea edx, [ebp-$1B]
00447AC9 B912000000 mov ecx, $00000012
|
00447ACE E889040000 call 00447F5C 可疑的Call,不能放过。这里要记下地址
00447AD3 C645F700 mov byte ptr [ebp-$09], $00
00447AD7 8D55D8 lea edx, [ebp-$28]
00447ADA 8B45FC mov eax, [ebp-$04] 好了,现在该OD上场了。用OD加载setup.exe。Ctrl+G来到00447ACE,F2下断点。
00447AAB . 6A 12 PUSH 12 (固定值,序列号的长度)
00447AAD . 68 73010000 PUSH 173 (固定值,算法里有用到)
00447AB2 . 8D55 DC LEA EDX,DWORD PTR SS:[EBP-24]
00447AB5 . 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
00447AB8 . 8B80 DC020000 MOV EAX,DWORD PTR DS:[EAX+2DC]
00447ABE . E8 A5E2FDFF CALL setup.00425D68
00447AC3 . 8B45 DC MOV EAX,DWORD PTR SS:[EBP-24] (用户名)
00447AC6 . 8D55 E5 LEA EDX,DWORD PTR SS:[EBP-1B] (存放序列号的地方,没有算之前,这里是空)
00447AC9 . B9 12000000 MOV ECX,12 ; |
00447ACE . E8 89040000 CALL setup.00447F5C <-----断在这里,注册算法就在这个里面
00447AD3 . C645 F7 00 MOV BYTE PTR SS:[EBP-9],0
00447AD7 . 8D55 D8 LEA EDX,DWORD PTR SS:[EBP-28]
00447ADA . 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
00447ADD . 8B80 E0020000 MOV EAX,DWORD PTR DS:[EAX+2E0]
00447AE3 . E8 80E2FDFF CALL setup.00425D68
00447AE8 . 8B45 D8 MOV EAX,DWORD PTR SS:[EBP-28]
00447AEB . E8 64C3FBFF CALL setup.00403E54
00447AF0 . 8BD0 MOV EDX,EAX (用户输入的序列号)
00447AF2 . 8D45 E5 LEA EAX,DWORD PTR SS:[EBP-1B] (这一步一执行,在EAX里直接就可以看到序列号了)
00447AF5 . E8 220AFCFF CALL setup.0040851C (序列号比较,就是一般的字符串比较)
00447AFA . 85C0 TEST EAX,EAX
00447AFC . 74 1A JE SHORT setup.00447B18 (相等就跳,然后是一些登记操作,我没分析了)
00447AFE . 6A 00 PUSH 0 ; /Arg1 = 00000000
00447B00 . 66:8B0D A87C4>MOV CX,WORD PTR DS:[447CA8] ; |
00447B07 . 33D2 XOR EDX,EDX ; |
00447B09 . B8 B47C4400 MOV EAX,setup.00447CB4 ; |ASCII "Invalid serial number"
00447B0E . E8 C9E4FFFF CALL setup.00445FDC ; \setup.00445FDC
好了,到这里我们就要分析CALL setup.00447F5C 里到底进行了些什么操作。
该IDA上场了,因为IDA看起来比OD标的更清楚些。。。
IDA加载setup,跳转到指定地址,输入 00447F5C,下面是IDA的反汇编代码,这里要说下的是delphi的参数传递,前两个参数用寄存器EAX,EDX后面两个参数用堆栈传递。
sub_447F5C proc near ; CODE XREF: sub_4474CC+1D7p
CODE:00447F5C ; sub_4474CC+2C3p ...
CODE:00447F5C
CODE:00447F5C var_c = dword ptr -0Ch
CODE:00447F5C sn = dword ptr -8 ;这里我重新命名了
CODE:00447F5C username = dword ptr -4 ;这里我重新命名了
CODE:00447F5C const_173 = dword ptr 8 ;这里我重新命名了
CODE:00447F5C const_12 = dword ptr 0Ch ;这里我重新命名了
CODE:00447F5C
CODE:00447F5C push ebp
CODE:00447F5D mov ebp, esp
CODE:00447F5F add esp, 0FFFFFFF4h
CODE:00447F62 push ebx
CODE:00447F63 push esi
CODE:00447F64 push edi
CODE:00447F65 mov [ebp+sn], edx ;保存序列号地址
CODE:00447F68 mov [ebp+username], eax ;保存用名名地址
CODE:00447F6B mov edi, [ebp+const_173]
CODE:00447F6E mov ebx, [ebp+const_12] ; ebx固定值 0x12(18)
CODE:00447F71 dec ebx
CODE:00447F72 test ebx, ebx
CODE:00447F74 jl short sum_SN
CODE:00447F76 inc ebx
CODE:00447F77 mov [ebp+var_c], 0
CODE:00447F7E mov esi, offset init_table
init_table 是一个整型数组,我已经把它弄出来了,见下面
从Init_SN开始依据输入的用户名对序列号进行初始化操作:
循环变量var_c从0开始对用户名字符串长度求余得rem,然后取出用户名字符串中的第rem位字符x,从init_table数组中取出第var_c位的数字y,然后把字符x放入序列号数组的第y位,循环18次。
CODE:00447F83
CODE:00447F83 Init_SN: ; CODE XREF: sub_447F5C+50
CODE:00447F83 mov eax, [ebp+username]
CODE:00447F86 call Length
CODE:00447F8B push eax ; username的长度
CODE:00447F8C mov eax, [ebp+var_c] ; eax <- var_c
CODE:00447F8F pop edx
CODE:00447F90 mov ecx, edx ; username的长度 给ecx
CODE:00447F92 cdq
CODE:00447F93 idiv ecx ; var_c / 长度
CODE:00447F95 inc edx ; edx余数
CODE:00447F96 mov eax, [ebp+username] ;eax用户名
CODE:00447F99 mov al, [eax+edx-1] ;取用户名的第 edx 个字符
CODE:00447F9D mov edx, [esi] ;从init_table取一个数字edx
CODE:00447F9F mov ecx, [ebp+sn] ;取序列号地址
CODE:00447FA2 mov [ecx+edx], al ;把取出的字符入到序列号的第edx位
CODE:00447FA5 inc [ebp+var_c] ;var_c+1
CODE:00447FA8 add esi, 4
CODE:00447FAB dec ebx
CODE:00447FAC jnz short Init_SN
循环结束,在序列号数组里填充了依据用户名字符中计算得的一组字符。
CODE:00447FAE
CODE:00447FAE sum_SN: ; CODE XREF: sub_447F5C+18
CODE:00447FAE mov ebx, [ebp+const_12]
CODE:00447FB1 dec ebx
CODE:00447FB2 test ebx, ebx
CODE:00447FB4 jl short loc_447FDF
CODE:00447FB6 inc ebx
CODE:00447FB7 mov eax, [ebp+sn]
从这里开始对序列号进行一些逻辑运算。循环变量i从0到18,从序列号数组中取了第i个字符a,变量di的初始值为173,字符a和di右移8的值进行异或操作后得到值x放入序列号的第i位,然后再对di进行如下操作edi = (x+di)*0CE6Dh + 58BFh
CODE:00447FBA
CODE:00447FBA XXX_SN1: ; CODE XREF: sub_447F5C+81
CODE:00447FBA xor edx, edx
CODE:00447FBC mov dl, [eax] ;从序列号数组中取出一个字符放入dl
CODE:00447FBE movzx ecx, di ;di的值给ecx也就是173
CODE:00447FC1 shr ecx, 8 ;173 右移8位 放入ecx
CODE:00447FC4 xor edx, ecx ;刚取出的符和右移后的值进行xor操作。
CODE:00447FC6 mov [eax], dl ;xor后的值再放回序列号数组中
CODE:00447FC8 xor edx, edx
CODE:00447FCA mov dl, [eax] ;计算出的字符
CODE:00447FCC add di, dx
CODE:00447FCF imul dx, di, 0CE6Dh
CODE:00447FD4 add dx, 58BFh
CODE:00447FD9 mov edi, edx ;上面的汇编代码可以认为是:edi = (edx+di)*0CE6Dh + 58BFh
CODE:00447FDB inc eax ;数组下标加1
CODE:00447FDC dec ebx
CODE:00447FDD jnz short XXX_SN1
CODE:00447FDF
CODE:00447FDF loc_447FDF: ; CODE XREF: sub_447F5C+58
CODE:00447FDF mov ebx, [ebp+const_12]
CODE:00447FE2 dec ebx
CODE:00447FE3 test ebx, ebx
CODE:00447FE5 jl short loc_448004
CODE:00447FE7 inc ebx
CODE:00447FE8 mov ecx, [ebp+sn]
下面的操作比较简单了,序列号数组中的每个字符对31h求余得余数rem,然后从ascii_table中取出第rem个字符放入序列号数组中相应的位置。
CODE:00447FEB
CODE:00447FEB QUERY_ASCII_TABLE: ; CODE XREF: sub_447F5C+A6
CODE:00447FEB xor eax, eax
CODE:00447FED mov al, [ecx] ;取序列号字符
CODE:00447FEF mov esi, 31h
CODE:00447FF4 xor edx, edx
CODE:00447FF6 div esi ;模31h求余 EDX
CODE:00447FF8 mov al, ds:ascii_table[edx] ;ascii_table中第余数edx个字符
CODE:00447FFE mov [ecx], al ;放入原来的序列号数组中
CODE:00448000 inc ecx
CODE:00448001 dec ebx
CODE:00448002 jnz short QUERY_ASCII_TABLE
通过上面的查表操作,把序列号转换成明文。基本上序列号的字符已经确定。
CODE:00448004
CODE:00448004 loc_448004: ; CODE XREF: sub_447F5C+89
CODE:00448004 mov ebx, [ebp+const_12]
CODE:00448007 dec ebx
CODE:00448008 test ebx, ebx
CODE:0044800A jl short loc_448030
CODE:0044800C inc ebx
CODE:0044800D mov eax, [ebp+sn]
CODE:00448010 mov esi, offset unk_44AB20 ;esi指向一张交换位置表
对序列号进行一些位位置上的交换。
遍历序列号,然后查表找到其要交换的位置,然后进行字符交换。
CODE:00448015
CODE:00448015 Exchang_XXX: ; CODE XREF: sub_447F5C+D2
CODE:00448015 mov dl, [eax] ;取出序列号的一个字符
CODE:00448017 mov ecx, [esi] ;需要交换的位置
CODE:00448019 mov edi, [ebp+sn]
CODE:0044801C mov cl, [edi+ecx] ;要交换位置的字符
CODE:0044801F mov [eax], cl
CODE:00448021 mov ecx, [esi]
CODE:00448023 mov edi, [ebp+sn]
CODE:00448026 mov [edi+ecx], dl ;上面的操作完成两个位置字符的交换
CODE:00448029 add esi, 4
CODE:0044802C inc eax
CODE:0044802D dec ebx
CODE:0044802E jnz short Exchang_XXX
CODE:00448030
CODE:00448030 loc_448030: ; CODE XREF: sub_447F5C+AE
CODE:00448030 pop edi
CODE:00448031 pop esi
CODE:00448032 pop ebx
CODE:00448033 mov esp, ebp
CODE:00448035 pop ebp
CODE:00448036 retn 8
CODE:00448036 sub_447F5C endp
终于分析完了,搞了一天,真是有点郁闷。把上面提到的三个表(数组)也附上。
这三张表如何得到的,我没有去分析,但是看地址DATA段的地址是0044A000大小为1000,这三个表都在DATA段内,所以估计这三个表(数组)是已经初始化的静态数组,不会变。
init_table
0044AAD8 10 00 00 00 02 00 00 00 00 00 00 00 03 00 00 00 .............
0044AAE8 0E 00 00 00 01 00 00 00 05 00 00 00 07 00 00 00 ............
0044AAF8 04 00 00 00 0F 00 00 00 09 00 00 00 0B 00 00 00 .............
0044AB08 11 00 00 00 08 00 00 00 0C 00 00 00 06 00 00 00 .............
0044AB18 0D 00 00 00 0A 00 00 00 11 00 00 00 02 00 00 00 ..............
0044AB28 03 00 00 00 00 00 00 00 0E 00 00 00 01 00 00 00 .............
0044AB38 0C 00 00 00 07 00 00 00 05 00 00 00 04 00 00 00 .............
0044AB48 0F 00 00 00 09 00 00 00 10 00 00 00 0B 00 00 00 .............
0044AB58 08 00 00 00 0D 00 00 00 0A 00 00 00 06 00 00 00 .............. ascii表
0044AAA4 31 51 32 57 33 45 34 52 35 54 36 59 37 55 38 49 1Q2W3E4R5T6Y7U8I
0044AAB4 39 41 31 53 32 44 33 46 34 47 35 48 36 4A 37 4B 9A1S2D3F4G5H6J7K
0044AAC4 38 4C 39 5A 58 31 43 32 56 33 42 34 4E 35 4D 36 8L9ZX1C2V3B4N5M6
0044AAD4 51 37 57 90 10 00 00 00 02 00 00 00 00 00 00 00 Q7W?..........
Exchang表
0044AB20 11 00 00 00 02 00 00 00 03 00 00 00 00 00 00 00 .............
0044AB30 0E 00 00 00 01 00 00 00 0C 00 00 00 07 00 00 00 .............
0044AB40 05 00 00 00 04 00 00 00 0F 00 00 00 09 00 00 00 .............
0044AB50 10 00 00 00 0B 00 00 00 08 00 00 00 0D 00 00 00 .............
0044AB60 0A 00 00 00 06 00 00 00 80 6F 40 00 F0 6E 40 00 .......€o@.餹@.
附上delphi的算法实现:算法没有优化,写的比较赖。将就一下了,今天一天都还没吃饭,先闪了。。。
Table_InitSN: array[0..$8F] of Byte = (
$10,00,00,00,$02,00,00,00,$00,00,00,00,$03,00,00,00,
$0E,00,00,00,$01,00,00,00,$05,00,00,00,$07,00,00,00,
$04,00,00,00,$0F,00,00,00,$09,00,00,00,$0B,00,00,00,
$11,00,00,00,$08,00,00,00,$0C,00,00,00,$06,00,00,00,
$0D,00,00,00,$0A,00,00,00,$11,00,00,00,$02,00,00,00,
$03,00,00,00,$00,00,00,00,$0E,00,00,00,$01,00,00,00,
$0C,00,00,00,$07,00,00,00,$05,00,00,00,$04,00,00,00,
$0F,00,00,00,$09,00,00,00,$10,00,00,00,$0B,00,00,00,
$08,00,00,00,$0D,00,00,00,$0A,00,00,00,$06,00,00,00);
Table_AscII: array [0..$3F] of Byte = (
$31,$51,$32,$57,$33,$45,$34,$52,$35,$54,$36,$59,$37,$55,$38,$49,
$39,$41,$31,$53,$32,$44,$33,$46,$34,$47,$35,$48,$36,$4A,$37,$4B,
$38,$4C,$39,$5A,$58,$31,$43,$32,$56,$33,$42,$34,$4E,$35,$4D,$36,
$51,$37,$57,$90,$10,$00,$00,$00,$02,$00,$00,$00,$00,$00,$00,$00);
Table_Exchang: array [0..$4F] of Byte = (
$11,$00,$00,$00,$02,$00,$00,$00,$03,$00,$00,$00,$00,$00,$00,$00,
$0E,$00,$00,$00,$01,$00,$00,$00,$0C,$00,$00,$00,$07,$00,$00,$00,
$05,$00,$00,$00,$04,$00,$00,$00,$0F,$00,$00,$00,$09,$00,$00,$00,
$10,$00,$00,$00,$0B,$00,$00,$00,$08,$00,$00,$00,$0D,$00,$00,$00,
$0A,$00,$00,$00,$06,$00,$00,$00,$80,$6F,$40,$00,$F0,$6E,$40,$00);
function GenKey(StrUserName:string;var Sn:array of Char;Const_173:Integer;Const_12:Integer):string;
var
Username: string;
Serial: PChar;
iCount: Integer;
i,len,rem: Integer;
a,b: Char;
PTable: PByte;
iecx: Integer;
idi: Word;
begin
Serial := Sn;
Username := StrUserName;
iCount := 0;
PTable := @Table_InitSN;
//初始公序列号数组
for i:=Const_12 downto 0 do
begin
len := Length(Username);
rem := iCount mod len;
Inc(rem);
a := UserName[rem];
(Serial+PTable^)^ := a;
Inc(iCount);
PTable := pbyte(Integer(PTable) + 4);
end;
//对序列号进行一些运算操作
idi := word(Const_173);
for i := Const_12 downto 0 do
begin
a := Serial^;
iecx := idi shr 8;
a := Char(Integer(a) xor iecx);
Serial^ := a;
idi := (idi + word(a)) * $0CE6D + $58BF;
Serial := Serial + 1;
end;
//对过查表把序列号变成明文
Serial := Sn;
for i := Const_12 downto 0 do
begin
a := Serial^;
rem := Byte(a) mod $31;
a := Char(Table_AscII[rem]);
Serial^ := a;
Serial := Serial + 1;
end;
//查表对序列号位置进行交换操作
Serial := Sn;
PTable := @Table_Exchang;
for i := Const_12 downto 0 do
begin
a := Serial^;
iecx := PTable^;
b := (PChar(Sn + iecx))^;
Serial^ := b;
(PChar(Sn + iecx))^ := a;
PTable := pbyte(Integer(PTable) + 4);
Serial := Serial + 1;
end;
end;
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!