能力值:
( LV2,RANK:10 )
|
-
-
2 楼
首先在ollydbg里定位到计算序列号的函数,给GetDlgItemText下断点、给check按钮下鼠标消息断点、找出出错提示字符串、在引用串的地方下断点,都很容易推断出计算序列号函数的位置,书里有前两种的描述这里就不多说。
下面是olldbg里截出的代码:
00401340 /$ 55 push ebp
00401341 |. 8B6C24 0C mov ebp, dword ptr [esp+C]
00401345 |. 56 push esi
00401346 |. 57 push edi
00401347 |. 8B7C24 18 mov edi, dword ptr [esp+18]
0040134B |. B9 03000000 mov ecx, 3
00401350 |. 33F6 xor esi, esi
00401352 |. 33C0 xor eax, eax
00401354 |. 3BF9 cmp edi, ecx
00401356 |. 7E 21 jle short TraceMe.00401379 ;跳转1
00401358 |. 53 push ebx
00401359 |> 83F8 07 /cmp eax, 7
0040135C |. 7E 02 |jle short TraceMe.00401360 ;跳转2
0040135E |. 33C0 |xor eax, eax
00401360 |> 33D2 |xor edx, edx
00401362 |. 33DB |xor ebx, ebx
00401364 |. 8A1429 |mov dl, byte ptr [ecx+ebp]
00401367 |. 8A98 30504000 |mov bl, byte ptr [eax+405030] ;取全局变量的值1
0040136D |. 0FAFD3 |imul edx, ebx
00401370 |. 03F2 |add esi, edx
00401372 |. 41 |inc ecx
00401373 |. 40 |inc eax
00401374 |. 3BCF |cmp ecx, edi
00401376 |.^ 7C E1 \jl short TraceMe.00401359 ;跳转3
00401378 |. 5B pop ebx
00401379 |> 56 push esi ; /<%ld>
0040137A |. 68 78504000 push TraceMe.00405078 ; |%ld ;取全局变量的值2
0040137F |. 55 push ebp ; |s
00401380 |. FF15 9C404000 call near dword ptr [<&USER32.wsprint>; \wsprintfA
00401386 |. 8B4424 1C mov eax, dword ptr [esp+1C]
0040138A |. 83C4 0C add esp, 0C
0040138D |. 55 push ebp ; /String2
0040138E |. 50 push eax ; |String1
0040138F |. FF15 04404000 call near dword ptr [<&KERNEL32.lstrc>; \lstrcmpA
00401395 |. F7D8 neg eax
00401397 |. 1BC0 sbb eax, eax
00401399 |. 5F pop edi
0040139A |. 5E pop esi
0040139B |. 40 inc eax
0040139C |. 5D pop ebp
0040139D \. C3 retn
我们就是要把这段代码搞到自己写的注册机程序里。
5个我加了中文注释的地方是首先需要处理的地方,它们引用的内存地址都是针对原程序来说的,移植到新程序里要做处理;其实3个跳转都是相对地址跳转,用一串db指令把机器码写到新程序里也能用,但可读性很差,所以换个简单的方式,给跳转的目的地址加上标签,jmp label即可;另一个是取全局变量的值的地方,如下:
mov bl, byte ptr [eax+405030]
这种一看地址不在堆栈范围的就是全局变量,eax稍后再解释,这里我们在ollydbg里查看地址405030的值,发现是8个字节的数值,用C语言语法描述就是:
BYTE byData[] = { 0x0C, 0x0A, 0x13, 0x09, 0x0C, 0x0B, 0x0A, 0x08};
另一个是push TraceMe.00405078,查看地址指向的值,用C表达就是 char *szData = "%ld";
|
能力值:
( LV2,RANK:10 )
|
-
-
3 楼
加上C风格的函数头,改写如下:
BYTE byData[] = { 0x0C, 0x0A, 0x13, 0x09, 0x0C, 0x0B, 0x0A, 0x08};
char *szData = "%ld";
int __declspec(naked) CalRegCode(char *szCode1,char *szName,int nNameLen)
{
__asm
{
push ebp;
mov ebp, dword ptr [esp+C]
push esi;
push edi;
mov edi, dword ptr [esp+18];
mov ecx, 0x03;
xor esi, esi;
xor eax, eax;
cmp edi, ecx;
jle short L1; ;跳转1
push ebx;
L3:
cmp eax, 0x07;
jle short L2; ;跳转2
xor eax, eax;
L2:
xor edx, edx;
xor ebx, ebx;
mov dl, byte ptr [ecx+ebp];
mov bl, byte ptr [eax+byData]; ;取全局变量的值1
imul edx, ebx;
add esi, edx;
inc ecx;
inc eax;
cmp ecx, edi;
jl short L3; ;跳转3
pop ebx;
L1:
push esi;
push szData; ;取全局变量的值2
push ebp;
call near dword ptr wsprint;
mov eax, dword ptr [esp+0x01C];
add esp, 0x00C;
push ebp;
push eax;
call near dword ptr lstrc;
neg eax;
sbb eax, eax;
pop edi;
pop esi;
inc eax;
pop ebp;
retn;
}
}
代码搬过来时别忘了给里面的数值加上0x前缀,因为在原来是16进制代码,放到C编译器里要加说明,当然自己手工转换成十进制数值也没问题。
关键字__declspec(naked)是让C编译器(我用vs2005)不要给函数生成入栈出栈的预处理后处理代码,普通的函数编译器肯定要生成这些代码的,但我们现在是搬来的代码已经做了这部分工作所以让编译器跳过这一步;这里有个要注意的地方是看看调用函数后栈怎么清理,Cdecl调用是由调用者清理,但是我们基本把asm代码限制在一个函数里,所以后面还要加一条 add esp,0xXX的指令才行,这就相当于把Cdecl调用改成Stdcall调用了,否则你就需要在调用CalRegCode函数的后面加这句 add esp的指令。
这里我没加,因为最后我发现这个函数需要改写。
函数原型的确定,主要由跟踪原程序,看调用前它压什么值入栈、进函数后取哪些堆栈的值、怎么用这些值来确定,有经验的可以不必调试程序,直接看汇编代码也能大致确定。
这个函数已经可以在新程序里运行,完成的功能和它在原程序里是一致的;这种方法是最简单的注册机编写方法,不需要深入分析代码逻辑、翻译成高级语言,把相关计算步骤的汇编代码拷贝过来、改造一下就可以给自己用。
|
能力值:
( LV2,RANK:10 )
|
-
-
4 楼
00000000000000
|
能力值:
( LV2,RANK:10 )
|
-
-
5 楼
int CalRegCode(char *szCode1,char *szName,int nNameLen);
这个函数,在原程序里是传入界面输入的用户名、序列号,然后计算出序列号,和输入的序列号对比,判断是否正确,而正确的序列号只存在这个函数里,没保存下来,出去后就没有了。
但我们现在需要的是函数返回一个根据用户名计算出来的、正确的序列号,函数声明应该是这么样的:
int CalRegCode_asm(char *szName,int nNameLen);
因为它计算出来的肯定是一个数值,所以可以把这个值直接返回,由调用者输出到界面上。
那么该怎么改呢,首先我们省略了一个参数,可以砍去和这个参数有关的代码;然后我们需要声明一个变量来保存计算出来的序列号,全局变量也可以挪到函数里面当局部变量用,这种情况下采用C函数里嵌套一段asm代码比较合适。
改造后如下:
int CalRegCode_asm(char *szName,int nNameLen)
{
BYTE byData[] = { 0x0C, 0x0A, 0x13, 0x09, 0x0C, 0x0B, 0x0A, 0x08};
int nResult = 0;
_asm
{
mov edi, nNameLen;
mov ecx, 0x03;
xor esi, esi;
xor eax, eax;
cmp edi, ecx;
jle short L1;
push ebx;
L3:
cmp eax, 0x07;
jle short L2;
xor eax, eax;
L2:
xor edx, edx;
xor ebx, ebx;
//mov dl, byte ptr [szName+ecx];
mov ebx, szName;
add ebx, ecx;
mov dl, byte ptr [ebx];
xor ebx,ebx;
//
mov bl, byte ptr byData[eax];
imul edx, ebx;
add esi, edx;
inc ecx;
inc eax;
cmp ecx, edi;
jl short L3;
pop ebx;
//result in esi;
mov dword ptr nResult,esi;
}
L1:
return nResult;
}
原来的这两行代码说明一下:
mov dl, byte ptr [ecx+ebp];
mov bl, byte ptr [eax+byData];
在ollydbg里调试、或者直接看上下代码可以知道ebp,byData的值是固定的,而ecx、eax是变化的,这其实是高级语言的数组在汇编中的实现方式,用一个C数组来举例:
int nData[3] = {1,2,3};
nData是数组的首地址,相当于[ecx+ebp]里的ebp,然后我们引用一个数组里的值是用nData[1]的方式,数组下标就相当于ecx;一维数组大致如此,表达式里不变的部分就是数组首地址,变的是数组下标,有些变化比如 [ecx+ebp+20],不变的部分是ebp+20,变的是ecx;二维数组与此类似,不过变化的有两个,比如[ebp+ecx+eax],多维数组由此类推。
此外,我用 mov dl, byte ptr [szName+ecx] 这一句达不到预想中的效果,只好做如下转换:
//mov dl, byte ptr [szName+ecx];
mov ebx, szName;
add ebx, ecx;
mov dl, byte ptr [ebx];
xor ebx,ebx;
//
请各位高手说明一下正确的语句该怎么写,距离当年在学校里学汇编语法已经很久,实在想不起来了。
省去一个参数(用户输入的序列号),同时把计算出来的数值返回(不必转换成字符串),就可以把后面的两个调用去掉(wsprintf、lstrc),代码一下子少了很多,就算新手也能看出程序逻辑、转换成C代码了。
|
能力值:
( LV2,RANK:10 )
|
-
-
6 楼
上面的函数已经符合我们的需求,可以直接用了。
要转换成C程序需要进一步分析程序逻辑,这时候在汇编代码旁用注释加上对应的C代码是个不错的做法:
int CalRegCode_asm(char *szName,int nNameLen)
{
BYTE byData[] = { 0x0C, 0x0A, 0x13, 0x09, 0x0C, 0x0B, 0x0A, 0x08};
int nResult = 0;
_asm
{
mov edi, nNameLen; // int vedi = nNameLen;
mov ecx, 0x03; // int vecx = 3;
xor esi, esi; // int vesi = 0;
xor eax, eax; // int veax = 0;
cmp edi, ecx; // if (vedi > vecx) ; if (nNameLen > 3)
jle short L1; //{
push ebx;
L3:
cmp eax, 0x07; // if (veax > 7)
jle short L2; // {
xor eax, eax; // veax = 0;} else
L2:
xor edx, edx; //{ vedx =0;
xor ebx, ebx; // vebx = 0;
//mov dl, byte ptr [szName+ecx];
mov ebx, szName; // char c1 = szName[vecx];
add ebx, ecx;
mov dl, byte ptr [ebx];
xor ebx,ebx;
//
mov bl, byte ptr byData[eax]; //char c2 = byData[veax];
imul edx, ebx; // long nTmp = c1 * c2;
add esi, edx; // vesi += nTmp;
inc ecx; // vecx++; veax++;
inc eax;
cmp ecx, edi; //if (vecx > nNameLen)
jl short L3; //跳回去,明显是个循环
pop ebx;
//result in esi;
mov dword ptr nResult,esi;
}
L1:
return nResult;
}
需要说明的是原汇编代码里的几处跳转,对应到高级语言里无非就是条件语句(if..else、switch)和循环语句(for、while等),比如这一段:
cmp eax, 0x07; // if (veax > 7)
jle short L2; // {
xor eax, eax; // veax = 0;} else
先拿eax的值和7比较,小于或等于就跳转,否则(大于)就往下,对应的C代码就是
if (veax > 7)
{
veax = 0;
}
else
{
.....
注意C代码的比较条件和汇编代码相反,这是为了让C代码和汇编代码在顺序结构上保持一致而特意做的,否则如果按照汇编代码直接翻译成 if (veax <= 7)的话,下面的指令xor eax, eax就得挪到后面,jmp到的代码就得挪上来,这样也不是不行但在对比汇编、C代码时很别扭,可读性不好。
还有这一段:
cmp ecx, edi; //if (vecx > nNameLen)
jl short L3; //跳回去,明显是个循环
当发现要跳回去的汇编代码,往往就是对应高级语言里的循环,具体循环条件就需要结合上下的代码来分析。
这些汇编代码和高级语言代码的对应关系,大致都有几种比较固定的模式,看多了就知道了;
平时用vc等编译器写程序,调试时把汇编代码打开,看看局部变量、全局变量、数组、结构等在汇编代码里怎么表示的,if、while、for等在汇编代码里怎么实现的,看熟了等你跟踪别人写的程序心理就有底了。
另外要注意不同的编译器对同一段高级语言编译出的机器码有时候区别很多,比如delphi和vc,这时候先用peid看看目标程序是用什么编译的比较好下手。
|
能力值:
( LV2,RANK:10 )
|
-
-
7 楼
最后翻译出来的C函数如下:
int CalRegCode(char *szName,int nNameLen)
{
BYTE byData[] = { 0x0C, 0x0A, 0x13, 0x09, 0x0C, 0x0B, 0x0A, 0x08};
int nResult = 0;
if (nNameLen > 3)
{
for (int i=3, j=0; i<= nNameLen; i++,j++)
{
nResult += (long)szName[i] * (long)byData[j%8];
}
}
return nResult;
}
此时我们完全理解了程序的逻辑,开头的这几句,
0040134B |. B9 03000000 mov ecx, 3
。。。
00401354 |. 3BF9 cmp edi, ecx
00401356 |. 7E 21 jle short TraceMe.00401379
一开始不知道为什么它为什么小于等于3就跳转,然后看到下面:
00401364 |. 8A1429 |mov dl, byte ptr [ecx+ebp]
。。。。
00401372 |. 41 inc ecx
就知道它取输入的用户名是从字符串的第四位(下标从0开始)开始的,前三个忽略不计,也就是说输入123rrr和890rrr的效果完全没区别。
此外,函数外面已经限制输入的用户名长度必须大于5个字符,所以这几行代码可以认为没有起作用。
这一段:
00401359 |> 83F8 07 cmp eax, 7
0040135C |. 7E 02 jle short TraceMe.00401360 ;跳转2
0040135E |. 33C0 xor eax, eax
为什么它大于7就清0呢?往下看:
00401367 |. 8A98 30504000 |mov bl, byte ptr [eax+405030]
。。。
00401373 |. 40 inc eax
原来[405030]这里的数值的数目有限制,具体是多少个呢,它每次加1,>7就是8了,也就是说byData有8个值(byte类型),从头到尾依次取值计算,到后面后又折回来,所以我用一个byData[j%8]完成这个判断。
|
能力值:
( LV2,RANK:10 )
|
-
-
8 楼
完毕,请新手一起学习,请高手指点,特别是上面我说的这一段:
用 mov dl, byte ptr [szName+ecx] 这一句达不到预想中的效果,只好做如下转换:
//mov dl, byte ptr [szName+ecx];
mov ebx, szName;
add ebx, ecx;
mov dl, byte ptr [ebx];
xor ebx,ebx;
//
请各位高手说明一下正确的语句该怎么写,距离当年在学校里学汇编语法已经很久,实在想不起来了。
|
能力值:
( LV2,RANK:10 )
|
-
-
9 楼
呵呵厉害啊,我到现在还是不明白
|
能力值:
( LV2,RANK:10 )
|
-
-
10 楼
我是菜鸟,学习了。
|
能力值:
( LV2,RANK:10 )
|
-
-
11 楼
我来看看,不过不太明白
|
能力值:
( LV2,RANK:10 )
|
-
-
12 楼
有点明白,需要继续整理,谢谢楼主了,不过注册机这块不是这里的新手能学的吧?呵呵
楼主可以的话还是写出怎么追注册码的方式吧,呵呵
|
能力值:
( LV2,RANK:10 )
|
-
-
13 楼
这个过程在书里有啊,你去买一本看就知道了。
另外,mov dl, byte ptr [szName+ecx] 这一句达不到预想中的效果知道怎么回事了。
其实如果szName是在函数这么声明 char szName[] = "adfdf" 的话,就可以取到正确的值,这时候串是放在栈里,是局部变量;
当声明成 char *szName= "adfdf" 时这个串是全局变量,而szName是传入的参数的话效果和这个一样,此时 mov dl, byte ptr [szName+ecx] 或者
mov dl, byte ptr szName[ecx] 取到的就是szData的地址(指针)。
而在C语法里,函数内部的
char *szData = "abcd" 和当参数传进来的 char *szData或char szData[]
以及函数内部声明的 char szData[] = "abcd"
效果没什么区别, 按szData[i]都能取到想要的值。
这个地方以前没注意到
|
|
|