什么游戏我就不说了,反正是金山的游戏。
该游戏加密算法相当简单,但是就是没有人去破解它,大家都以为加密算法很难,所以都不去破
解,致使这个简单的加密算法一直逍遥法外,这就像0day挖掘一样,不要放过任何一个可能性,否
则你就等于放弃了一次0day的机会,哪怕它多么的简单。
开始:
OD attach游戏进程,然后OD崩溃,证明该游戏有反调试,然后,确定游戏没有内核保护之后开
strongod过反调试,然后bp send,返回游戏中走两步,断下后alt+f9回到游戏领空,发现:
从图中可得知EBX中存储的就是密文缓冲区首地址,接下来那就找EBX中的值是从哪获取的,然后
向上找,找到这么一处:
可知是从EDX中获取,然后再向上找EDX中的值是如何获取,依次类推找到这一处:
经过F8反复调试,发现call eax过后的返回值eax中存储的正是密文缓冲区地址,然后mov
edx,eax,将地址写入edx中,接着sub edx,2,将缓冲区扩充2字节,add edi,2是将缓冲区长
度增加2字节,因为edi中存储的就是缓冲区长度,不要问我为什么,在OD中反复跟踪几次就明白
了,最后一句:mov word ptr ds:[edx],di是将长度写入缓冲区结尾,这也就是说发送的封包中
最后2字节是代表封包长度,而且是未加密的,解密时一定要注意把这两字节剔除,否则就错了。
从上面的代码可以看出call eax是获取缓冲区的,那么F7进入后看看,代码如下:
由此可知缓冲区由ecx中来,偏移14,那么继续往上找,找到最后发现这么一处:
反复调试过后发现memcpy将明文复制到一个缓冲区中,而该缓冲区正是前面ecx偏移14那个地方
的值,好了,这下找到了缓冲区的由来了,接下来继续调试……
经过翻来覆去调试了N遍后发现一个惊人的秘密:
当call ecx执行之前,封包的缓冲区是4c 00 00 00……,执行call ecx过后,封包的缓冲区就变
为一堆乱七八糟的数字了,这证明call ecx就是传说中的加密算法。
接着,F7步入传说中的加密算法看看:
然后继续F7步入上图的那个call就到达加密算法入口地址了,接下来就是要分析加密过程了,加密
算法函数如下:
.text:0079E670 arg_0 = dword ptr 4
.text:0079E670 arg_4 = dword ptr 8
.text:0079E670 arg_8 = dword ptr 0Ch
push ebx
.text:0079E671 push ebp
.text:0079E672 mov ebp, [esp+8+arg_8]
.text:0079E676 mov ecx, [ebp+0]
.text:0079E679 push esi
.text:0079E67A mov esi, [esp+0Ch+arg_0]
.text:0079E67E mov ebx, esi
.text:0079E680 and ebx, 3
.text:0079E683 shr esi, 2
.text:0079E686 push edi
.text:0079E687 mov edi, [esp+10h+arg_4]
.text:0079E68B jz short loc_79E6B5
.text:0079E68D lea ecx, [ecx+0]
.text:0079E690
.text:0079E690 loc_79E690: ; CODE XREF: sub_79E670+43j
.text:0079E690 sub esi, 1
.text:0079E693 lea eax, [ecx+esi]
.text:0079E696 xor edx, edx
.text:0079E698 div dword_91E9B4
.text:0079E69E add edi, 4
.text:0079E6A1 mov ecx, dword_9190F8[edx*4]
.text:0079E6A8 add ecx, 2E6D23C1h
.text:0079E6AE xor [edi-4], ecx
.text:0079E6B1 test esi, esi
.text:0079E6B3 ja short loc_79E690
.text:0079E6B5
.text:0079E6B5 loc_79E6B5: ; CODE XREF: sub_79E670+1Bj
.text:0079E6B5 xor edx, edx
.text:0079E6B7 mov eax, ebx
.text:0079E6B9 div dword_91E9B4
.text:0079E6BF xor ecx, dword_9190F8[edx*4]
.text:0079E6C6 test ebx, ebx
.text:0079E6C8 jbe short loc_79E6DF
.text:0079E6CA lea ebx, [ebx+0]
.text:0079E6D0
.text:0079E6D0 loc_79E6D0: ; CODE XREF: sub_79E670+6Dj
.text:0079E6D0 xor [edi], cl
.text:0079E6D2 sub ebx, 1
.text:0079E6D5 add edi, 1
.text:0079E6D8 shr ecx, 8
.text:0079E6DB test ebx, ebx
.text:0079E6DD ja short loc_79E6D0
.text:0079E6DF
.text:0079E6DF loc_79E6DF: ; CODE XREF: sub_79E670+58j
.text:0079E6DF mov eax, [ebp+0]
.text:0079E6E2 mov ecx, eax
.text:0079E6E4 shl ecx, 5
.text:0079E6E7 sub ecx, eax
.text:0079E6E9 pop edi
.text:0079E6EA add ecx, 8088405h
.text:0079E6F0 pop esi
.text:0079E6F1 mov [ebp+0], ecx
.text:0079E6F4 pop ebp
.text:0079E6F5 mov eax, 1
.text:0079E6FA pop ebx
.text:0079E6FB retn
用OD反复调试过后分析的加密过程:
1.从某一基址处获取初始密钥
2.将缓冲区大小(未添加2字节之前的大小)右移2位
3.循环,循环次数为右移2位后的结果
循环中:
a.密钥=密钥+循环次数
b.取余=密钥%0x162f
c.密钥=取余*4+0x9190f8
d.从密钥地址处获取一个新的密钥
e.新密钥+=0x2e6d23c1
f.取明文后4字节与新密钥异或
4.and结果=将缓冲区大小(未添加2字节之前的大小)and 3
5.and取余=and结果%0x162f
6.地址1=and取余*4+0x9190f8
7.从密钥地址处获取一个新的密钥
8.从地址1处取值,然后与密钥异或,生成新密钥
9.将密钥低8位与剩下的最后一个字节进行异或
至此加密完成。
加密过程不难就是以4字节为一个密钥对封包进行异或,密钥的生成就是按照上述的过程生成的,
后一个密钥的生成与前一个密钥相关,也就是说只要有一个密钥生成错误,那么后面的密钥都将生
成错误,而初始密钥是存储在某个基址处的,最终用OD按照开始的向上寻找的办法就可以找到,
解密过程其实就是加密过程,因为a^b=c 知道b和c后可以b^c=a,所以该函数给他密文他出来的
就是明文,给他明文他出来的就是密文,最后用C++写出:
void encrypt(HANDLE handle,BYTE buf[],int len)
{
int a=len && 3;
int b=len >> 2;
DWORD encryptKey=NULL;//密钥
DWORD keyBaseAddr=0xd2a4ec;//密钥基址
DWORD keyAddress;
if(!::ReadProcessMemory(handle,(LPVOID)keyBaseAddr,&keyAddress,sizeof(DWORD),NULL)){
printf("密钥基址读取失败,错误代码:%d\n",::GetLastError());
return;
}
keyAddress+=0x1c;
if(!::ReadProcessMemory(handle,(LPVOID)keyAddress,&keyAddress,sizeof(DWORD),NULL)){
printf("密钥内存地址读取失败,错误代码:%d\n",::GetLastError());
return;
}
keyAddress+=0xc;
if(!::ReadProcessMemory(handle,(LPVOID)keyAddress,&encryptKey,sizeof(DWORD),NULL)){
printf("密钥读取失败,错误代码:%d\n",::GetLastError());
return;
}
int d;
int count=0;
while(b--){
encryptKey=encryptKey+b;
d=encryptKey%0x162f;
encryptKey=(d*4+0x9190f8);
if(!::ReadProcessMemory(handle,(LPVOID)encryptKey,&encryptKey,sizeof(DWORD),NULL)){
printf("生成密钥失败,错误代码:%d\n",::GetLastError());
}
encryptKey+=0x2e6d23c1;
DWORD dwBuf;
memcpy(&dwBuf,(LPVOID)&buf[count],4);
dwBuf=dwBuf^encryptKey;
memcpy(&buf[count],(LPVOID)&dwBuf,4);
count+=4;
};
int f=a%0x162f;
DWORD g=f*4+0x9190f8;
if(!::ReadProcessMemory(handle,(LPVOID)g,&g,sizeof(DWORD),NULL)){
printf("生成密钥失败,错误代码:%d\n",::GetLastError());
}
encryptKey=encryptKey^g;
if(a>0)
{
while(a--)
{
BYTE by=buf[count];
_asm{
push eax
push ebx
mov al,byte ptr by
mov ebx,encryptKey
xor al,bl
mov byte ptr by,al
pop ebx
pop eax
}
buf[count]=by;
count++;
encryptKey=encryptKey >> 8;
}
}
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)