crackme007
名称 | 值 |
---|---|
软件名称 | aLoNg3x.2.exe |
加壳方式 | 无 |
保护方式 | serial |
编译语言 | Delphi |
调试环境 | win10 64位 |
使用工具 | x32dbg,ida pro,PEid,DarkDe4 |
破解日期 | 2025-06-10 |
脱壳
1. 先用PEid查壳
- 查到无壳
寻找Serial
- 查询到编程语言为Delphi
- 导出Delphi符号表信息到x32dbg,用IDA Pro 打开程序
- 添加常见Delphi库的符号
常见Delphi库 |
---|
b32vcl |
bds2006 |
delphi |
d5vcl |
d4vcl |
c4vcl |
bds |
快捷键Shift+F5,如下图所示
导出map文件,
File->Produce file->Create MAP file...
用DarkDe4打开
PE文件
查看窗体结构
寻找flag,点击
About-Help
按钮,发现Flag:隐藏Register和...again!!!:p两个按钮
在DarkDe4中可以发现Register的id为:
2CC
,Again按钮的id为:2E8
查找Register的调用
鼠标右键->搜索->当前模块->常数
输入2CC
选中
地址=00442FC4反汇编=mov eax,dword ptr ds:[ebx+2CC]
跳转到SetVisible函数的调用处,发现关键函数调用call <along3x.2.sub_4429A8>
分析函数调用
call <along3x.2.sub_4429A8>
add esp,FFFFFFF4 ;开辟函数栈空间
push ebx ;保存寄存器值
push esi ;保存寄存器值
push edi ;保存寄存器值
mov dword ptr ss:[ebp-8],ecx ;nome字符串赋值给局部变量
mov dword ptr ss:[ebp-4],edx ;codice字符串的long值
mov edi,eax ;参数key赋值给edi
mov eax,dword ptr ss:[ebp-8] ;取nome字符串首地址
call <along3x.2.System::__linkproc__ LStrAddRef(void)>
xor eax,eax ;清空eax
push ebp ;栈基入栈
push <along3x.2.loc_442A7A> ;try
push dword ptr fs:[eax] ; try
mov dword ptr fs:[eax],esp ; try
mov eax,dword ptr ss:[ebp-8] ;取nome字符串首地址
call <along3x.2.__linkproc__ LStrLen> ;求nome字符串的长度
cmp eax,4 ;nome字符串的长度与4比较
jle <along3x.2.end> ; nome字符串长度小于等于4跳转到end
xor ebx,ebx ;清空ebx
mov eax,dword ptr ss:[ebp-8] ;取nome字符串首地址
call <along3x.2.__linkproc__ LStrLen> ;求nome字符串的长度
test eax,eax ;nome判断字符串长度是否为0
jle <along3x.2.loc_442A26>;nome字符串长度为0则跳转
mov dword ptr ss:[ebp-C],eax; 字符串长度赋值给外循环变量
mov esi,1; 字符串索引赋初值为1
along3x.2.loc_4429F6:mov eax,dword ptr ss:[ebp-8] ;取nome字符串首地址
call <along3x.2.__linkproc__ LStrLen> ;求nome字符串长度
cmp eax,1 ;字符串长度与1比较
jl <along3x.2.loc_442A20> ;小于1则跳转
along3x.2.loc_442A03: mov edx,dword ptr ss:[ebp-8] ;取nome字符串首地址
movzx edx,byte ptr ds:[edx+esi-1] ;edx = nome[esi-1]
mov ecx,dword ptr ss:[ebp-8] ;取nome字符串首地址
movzx ecx,byte ptr ds:[ecx+eax-1] ;edx = nome[eax-1],eax初值为字符串长度
imul edx,ecx ;edx = nome[esi-1]*nome[eax-1]
imul edx,edi ; edx = nome[esi-1]*nome[eax-1]*key
add ebx,edx ;ebx+= edx
dec eax ;内循环变量自减1
test eax,eax ;判断内循环变量是否为0
jne <along3x.2.loc_442A03> ;循环变量不为0,循环继续
inc esi ;索引自增1
dec dword ptr ss:[ebp-C] ;外循环变量自减1
jne <along3x.2.loc_4429F6> ;外循环变量不为0,循环继续
mov eax,ebx ;累加结果赋值给eax
cdq ;将edx清零
xor eax,edx ;异或0,结果为原值
sub eax,edx ;减去0结果为原值
mov ecx,A2C2A
cdq ;将edx清零
idiv ecx ; eax/A2C2A,商入eax,余数入edx
mov ebx,edx; ebx = eax%A2C2A
mov eax,dword ptr ss:[ebp-4]; 取strTolong(codice)
mov ecx,59
cdq
idiv ecx ; eax/0x59,商入eax,余数入edx
mov ecx,eax ; ecx = strTolong(codice)/0x59
mov eax,dword ptr ss:[ebp-4] 取strTolong(codice)
mov esi,50
cdq ;将edx清零
idiv esi ; eax/0x50,商入eax,余数入edx
add ecx,edx ; ecx = strTolong(codice)/0x59 + eax%0x50
inc ecx ;ecx 自增1
mov dword ptr ss:[ebp-4],ecx ; ecx赋值给局部变量
cmp ebx,dword ptr ss:[ebp-4] ;比较strTolong(codice)/0x59 + eax%0x50 + 1 与 ebx
jne <along3x.2.loc_442A5E> ; 不相等则跳转
mov bl,1
jmp <along3x.2.loc_442A64>
xor ebx,ebx
jmp <along3x.2.loc_442A64>
xor ebx,ebx
xor eax,eax
pop edx
pop ecx
pop ecx
mov dword ptr fs:[eax],edx
push <along3x.2.loc_442A81>
lea eax,dword ptr ss:[ebp-8]
call <along3x.2.System::__linkproc__ LStrClr(System::AnsiString &)>
ret
jmp <along3x.2.System::__linkproc__ HandleFinally(void)>
jmp <along3x.2.loc_442A71>
mov eax,ebx ;ebx的值赋值给eax返回带出
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
- 综上分析出算法,写下c++代码注册机,输入nome时,用注册机结果输入codice,按下Register按钮
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int serial2(char* nome,int key)
{
int ans = 0;
int len = strlen(nome);
if (len <= 4)
{
return -1;
}
for (int i = 0; i < len; i++)
{
for (int j = len - 1; j >= 0; j--)
{
ans += nome[i] * nome[j] * key;
}
}
ans = ans % 0xA2C2A;
//暴力猜测codice的值
int ans2 = ans - 1;
for (int i = 0; i < 0x50; i++)
{
int j = ans2 - i;
int codice = j * 0x59 + i;
if ((codice / 0x59) + (codice % 0x50) == ans2)
{
return codice;
}
}
return -1;
}
- key的取值问题,key的取值为dword ptr ds:[<dword_key>]
00442FB4 | A1 30584400| mov eax,dword ptr ds:[<dword_key>] |
- 下列代码给 key赋值
00442F81 |call<along3x.2.Libmain::TWindowDesigner::SelectAll(void)> |
00442F86 | mov dword ptr ds:[<dword_key>],eax |
- 要进入key赋值代码,需要以下代码顺序执行,需要满足codice输入框输入非纯数字值
00442F53 | mov eax,dword ptr ss:[ebp-8] ;取codice字符串首地址
00442F56 | lea edx,dword ptr ss:[ebp-4] ;取局部变量的首地址做返回值
00442F59 | call <along3x.2.System::__linkproc__ ValLong(void)>; codice字符转Long
00442F5E | mov esi,eax ;结果赋值给esi
00442F60 | cmp dword ptr ss:[ebp-4],0 ;结果为0表示转换成功,不为0表示转换失败
00442F64 | je <along3x.2.loc_442F9D>
00442F66 | mov eax,along3x.2.443038
00442F6B | call <along3x.2.Dialogs::ShowMessage(System::AnsiString)>;弹出提示框
00442F70 | lea edx,dword ptr ss:[ebp-8]
00442F73 | mov eax,dword ptr ds:[ebx+2DC]
00442F79 | call <along3x.2.TControl::GetText(void)>
00442F7E | mov eax,dword ptr ss:[ebp-8];;取codice字符串首地址
00442F81 | call <along3x.2.Libmain::TWindowDesigner::SelectAll(void)>;对codice做某种运算
00442F86 | mov dword ptr ds:[<dword_key>],eax
- 分析
call <along3x.2.Libmain::TWindowDesigner::SelectAll(void)>
00442A8C | push ebp
00442A8D | mov ebp,esp
00442A8F | push ecx
00442A90 | push ebx
00442A91 | push esi
00442A92 | push edi
00442A93 | mov dword ptr ss:[ebp-4],eax ;codice字符串首地址赋值给局部变量
00442A96 | mov eax,dword ptr ss:[ebp-4]
00442A99 | call <along3x.2.System::__linkproc__ LStrAddRef(void)>
00442A9E | xor eax,eax
00442AA0 | push ebp
00442AA1 | push <along3x.2.loc_442B21>
00442AA6 | push dword ptr fs:[eax]
00442AA9 | mov dword ptr fs:[eax],esp
00442AAC | mov eax,dword ptr ss:[ebp-4];取codice字符串首地址
00442AAF | call <along3x.2.__linkproc__ LStrLen> ;求codice字符串长度
00442AB4 | cmp eax,5;比较字符串长度与5
00442AB7 | jle <along3x.2.loc_442AF6> ;字符串长度小于等于5跳出
00442AB9 | mov esi,37B;esi初值为37B
00442ABE | mov eax,dword ptr ss:[ebp-4] ;取codice首地址
00442AC1 | call <along3x.2.__linkproc__ LStrLen> ;求codice长度
00442AC6 | mov ebx,eax ;ebx = codice长度
00442AC8 | dec ebx ;ebx自减1
00442AC9 | test ebx,ebx ;判断ebx是否为0
00442ACB | jle <along3x.2.loc_442AF8> ;ebx为0跳出
00442ACD | mov ecx,1 ;ecx索引赋初值为1
00442AD2 | mov eax,dword ptr ss:[ebp-4];取codice字符串首地址
00442AD5 | movzx eax,byte ptr ds:[eax+ecx] ;eax = codice[ecx]
00442AD9 | mov edi,11
00442ADE | xor edx,edx
00442AE0 | div edi ;codice[ecx]/0x11,商入eax,余数入edx
00442AE2 | inc edx ;edx = codice[ecx]%0x11 + 1
00442AE3 | mov eax,dword ptr ss:[ebp-4] ;取codice首地址
00442AE6 | movzx eax,byte ptr ds:[eax+ecx-1] ;eax = codice[ecx - 1]
00442AEB | imul edx,eax ;(codice[ecx]%0x11 + 1) * codice[ecx - 1]
00442AEE | add esi,edx ;esi += (codice[ecx]%0x11 + 1) * codice[ecx - 1]
00442AF0 | inc ecx ;索引自增1
00442AF1 | dec ebx ;循环变量自减1,初值为codice字符串长度
00442AF2 | jne <along3x.2.loc_442AD2> ;循环变量不为0循环继续
00442AF4 | jmp <along3x.2.loc_442AF8>
00442AF6 | xor esi,esi
00442AF8 | mov eax,esi ;esi赋值给eax
00442AFA | mov ecx,7148
00442AFF | cdq
00442B00 | idiv ecx
00442B02 | mov eax,edx ;eax = eax%0x7148
00442B04 | cdq ;edx清零
00442B05 | xor eax,edx ;异或0,结果为原值
00442B07 | sub eax,edx ;减去0,结果为原值
00442B09 | mov ebx,eax ;ebx = eax
00442B0B | xor eax,eax ;清空eax
00442B0D | pop edx
00442B0E | pop ecx
00442B0F | pop ecx
00442B10 | mov dword ptr fs:[eax],edx
00442B13 | push <along3x.2.loc_442B28>
00442B18 | lea eax,dword ptr ss:[ebp-4]
00442B1B | call <along3x.2.System::__linkproc__ LStrClr(System::AnsiString &)>
00442B20 | ret
00442B21 | jmp <along3x.2.System::__linkproc__ HandleFinally(void)>
00442B26 | jmp <along3x.2.loc_442B18>
00442B28 | mov eax,ebx ;ebx赋值给eax,以便返回带出
00442B2A | pop edi
00442B2B | pop esi
00442B2C | pop ebx
00442B2D | pop ecx
00442B2E | pop ebp
00442B2F | ret
- 综上分析出算法,写下c++代码注册机,输入codice(长度大于5的非纯数字)时,按下
Register
按钮。再输入长度大于4的nome,用注册机算出codice并填入,按下Register
按钮
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int serial1(char* codice)
{
int ans = 0x37B;
int len = strlen(codice);
if (len <= 5)
{
return -1;
}
for (int i = 1; i < len; i++)
{
ans += (codice[i] % 0x11 + 1)* codice[i - 1];
}
ans = ans % 0x7148;
return ans;
}
int serial2(char* nome,int n)
{
int ans = 0;
int len = strlen(nome);
if (len <= 4)
{
return -1;
}
for (int i = 0; i < len; i++)
{
for (int j = len - 1; j >= 0; j--)
{
ans += nome[i] * nome[j] * n;
}
}
ans = ans % 0xA2C2A;
//暴力猜测codice的值
int ans2 = ans - 1;
for (int i = 0; i < 0x50; i++)
{
int j = ans2 - i;
int codice = j * 0x59 + i;
if ((codice / 0x59) + (codice % 0x50) == ans2)
{
return codice;
}
}
return -2;
}
int main()
{
printf("输入非数字型的codice:\r\n");
char codice[1024];
scanf("%s", codice);
int ans = serial1(codice);
if (ans == -1)
{
printf("字符串长度不能小于等于5\r\n");
return 0;
}
printf("序列号为:%d\r\n", ans);
printf("输入nome:\r\n");
char nome[1024] = { 0 };
scanf("%s", nome);
ans = serial2(nome, ans);
if (ans == -1)
{
printf("字符串长度不能小于等于4\r\n");
return 0;
}
if (ans == -2)
{
printf("计算序列号发生错误\r\n");
return 0;
}
printf("codice为%d\r\n",ans);
return 0;
}
- 点击
Register
按钮,Register
按钮消失,出现again
按钮 - 利用darkde4中AgainClick函数Rva为0x004430BC查看again的点击响应函数,发现流程与
Register
按钮相同
总结一下crackme步骤
- 在codice输入长度大于5的非纯数字字符串,点击
register
按钮 - nome输入长度大于4的字符串,注册机产生的codice值输入到codice编辑框,点击
register
按钮 - 在codice输入长度大于5的非纯数字字符串,点击
again
按钮 - nome输入长度大于4的字符串,注册机产生的codice值输入到codice编辑框,点击
again
按钮