crackme007

发布于:2025-06-11 ⋅ 阅读:(24) ⋅ 点赞:(0)

crackme007

名称
软件名称 aLoNg3x.2.exe
加壳方式
保护方式 serial
编译语言 Delphi
调试环境 win10 64位
使用工具 x32dbg,ida pro,PEid,DarkDe4
破解日期 2025-06-10

脱壳

1. 先用PEid查壳

01-PEid查壳.jpg

  • 查到无壳

寻找Serial

  • 查询到编程语言为Delphi
  • 导出Delphi符号表信息到x32dbg,用IDA Pro 打开程序
    02-ida pro 打开pe文件
  • 添加常见Delphi库的符号
常见Delphi库
b32vcl
bds2006
delphi
d5vcl
d4vcl
c4vcl
bds
  • 快捷键Shift+F5,如下图所示
    03-IDA Pro Signatures

  • 导出map文件,File->Produce file->Create MAP file...
    03-导出map文件

  • 用DarkDe4打开PE文件
    04-DarDe4查看Delphi程序

  • 查看窗体结构
    05-DarDe4查看Delphi窗体结构

  • 寻找flag,点击 About-Help按钮,发现Flag:隐藏Register和...again!!!:p两个按钮
    08-Find-Flag

  • 在DarkDe4中可以发现Register的id为:2CC,Again按钮的id为:2E8
    04-DarDe4查看Delphi控件ID

  • 查找Register的调用 鼠标右键->搜索->当前模块->常数 输入2CC
    08-查找常数2CC

  • 选中地址=00442FC4反汇编=mov eax,dword ptr ds:[ebx+2CC] 跳转到SetVisible函数的调用处,发现关键函数调用call <along3x.2.sub_4429A8>
    09-分析SetVisible相关代码

  • 分析函数调用call <along3x.2.sub_4429A8>
    09-分析关键算法

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)>
    10-分析key的产生
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按钮