编程语言:C,汇编
编译环境:VC++6.0,MASM
建议工具:OD
时间:2008.10.28
程序的自我效验,就是程序自己检测自身的完整性,主要用于抵抗软件的动态调试(如断点),内存补丁,暴力破解等。
本文旨在介绍程序自我效验的基本方法,希望可以起到抛砖引玉的作用。(菜文一篇,高手飘过)
我们先来看一个例子:
#include<windows.h>
#include<stdio.h>
/***********************************************************
自我效验测试程序
***********************************************************/
DWORD Calculation(DWORD StartAddr,DWORD dwCodeLength);
void CheckSelfRelease(DWORD StartAddr,DWORD dwCodeLength);
void TerminateCurrentProcess();
DWORD Calculation(DWORD StartAddr,DWORD dwCodeLength)
{
_asm
{
xor eax,eax
xor ebx,ebx
mov esi,StartAddr
mov ecx,dwCodeLength
Label001:
mov bl,byte ptr [esi]
add eax,ebx
inc esi
loop Label001
}
}
void TerminateCurrentProcess()
{
TerminateProcess(GetCurrentProcess(),0);
}
void CheckSelfRelease(DWORD StartAddr,DWORD dwCodeLength)
{
_asm
{
xor eax,eax
xor ebx,ebx
mov esi,StartAddr
mov ecx,dwCodeLength
Label001:
mov bl,byte ptr [esi]
add eax,ebx
inc esi
loop Label001
cmp eax,0x0B59 //0x0B59是提前计算好的
je Label002
call TerminateCurrentProcess
Label002:
}
}
void main()
{
DWORD StartAddr=0;
DWORD EndAddr=0;
DWORD dwCodeLength=0;
DWORD dwChecksum=0;
_asm
{
Start:
xor eax,eax
xor ebx,ebx
inc eax
add ebx,eax
add eax,ebx
mov StartAddr,offset Start
mov EndAddr,offset End
End:
}
dwCodeLength=EndAddr-StartAddr;
CheckSelfRelease(StartAddr,dwCodeLength);
MessageBoxA(NULL,"0K!","INFO",MB_OK);
}
上面的程序用累加得到指定程序段的效验和,你完全可以使用其它的方法来计算效验和,
可以用SUB,ROR,ROL 。。。等等。我只是用尽可能简单的过程来描述。
如果内存中程序的指定部分没有被改动,就会输出一MessageBox,否则程序会结束自己。
到这读者可能要问0x0B59是怎样得到的,那就看下面的程序吧:
#include<windows.h>
#include<stdio.h>
DWORD __stdcall Calculation(DWORD StartAddr,DWORD dwCodeLength); //计算效验和
void __stdcall CheckSelf(DWORD StartAddr,DWORD dwCodeLength,DWORD dwChecksum);
void __stdcall TerminateCurrentProcess();
DWORD __stdcall Calculation(DWORD StartAddr,DWORD dwCodeLength)
{
_asm
{
xor eax,eax
xor ebx,ebx
mov esi,StartAddr
mov ecx,dwCodeLength
Label001:
mov bl,byte ptr [esi]
add eax,ebx
inc esi
loop Label001
}
}
void __stdcall TerminateCurrentProcess()
{
TerminateProcess(GetCurrentProcess(),0);
}
void __stdcall CheckSelf(DWORD StartAddr,DWORD dwCodeLength,DWORD dwChecksum)
{
_asm
{
xor eax,eax
xor ebx,ebx
mov esi,StartAddr
mov ecx,dwCodeLength
Label001:
mov bl,byte ptr [esi]
add eax,ebx
inc esi
loop Label001
cmp eax,dwChecksum
je Label002
call TerminateCurrentProcess
Label002:
}
}
void main()
{
DWORD StartAddr=0;
DWORD EndAddr=0;
DWORD dwCodeLength=0;
DWORD dwChecksum=0;
_asm
{
Start:
xor eax,eax
xor ebx,ebx
inc eax
add ebx,eax
add eax,ebx
mov StartAddr,offset Start
mov EndAddr,offset End
End:
}
dwCodeLength=EndAddr-StartAddr;
dwChecksum=Calculation(StartAddr,dwCodeLength);
CheckSelf(StartAddr,dwCodeLength,dwChecksum);
MessageBoxA(NULL,"OK!","INFO",MB_OK);
}
程序中的Calculation函数用于计算效验和,其他与第一个程序基本相同。0x0B59就是用这中方法得到的。
我们在OD中分析软件的时候,仅仅下了几个断点,按下 F9,程序就结束了,界面没有了,什么也没有了,十有八九是因为软件用了自我效验。
上面的方法只对指定程序段进行了一次效验,很脆弱,可不可以实时监控程序的完整性呢?
答案是肯定的,我的想法是用CreateThread()来创建一子线程,实现实时监控:
#include<windows.h>
#include<stdio.h>
/***********************************************************
自我效验测试程序,用于监视内存中程序的完整性
***********************************************************/
DWORD Calculation(DWORD StartAddr,DWORD dwCodeLength);
void CheckSelf(DWORD StartAddr,DWORD dwCodeLength,DWORD dwChecksum);
void TerminateCurrentProcess();
DWORD Calculation(DWORD StartAddr,DWORD dwCodeLength)
{
_asm
{
xor eax,eax
xor ebx,ebx
mov esi,StartAddr
mov ecx,dwCodeLength
Label001:
mov bl,byte ptr [esi]
add eax,ebx
inc esi
loop Label001
}
}
void TerminateCurrentProcess()
{
TerminateProcess(GetCurrentProcess(),0);
}
void CheckSelf(DWORD StartAddr,DWORD dwCodeLength,DWORD dwChecksum)
{
_asm
{
xor eax,eax
xor ebx,ebx
mov esi,StartAddr
mov ecx,dwCodeLength
Label001:
mov bl,byte ptr [esi]
add eax,ebx
inc esi
loop Label001
cmp eax,dwChecksum
je Label002
call TerminateCurrentProcess
Label002:
}
}
void CheckSelfRelease(DWORD StartAddr,DWORD dwCodeLength)
{
_asm
{
xor eax,eax
xor ebx,ebx
mov esi,StartAddr
mov ecx,dwCodeLength
Label001:
mov bl,byte ptr [esi]
add eax,ebx
inc esi
loop Label001
cmp eax,0x0AF9
je Label002
call TerminateCurrentProcess
Label002:
}
}
void ThreadStartRoutine(DWORD * StartAddr)
{
for(;;)
CheckSelfRelease(*StartAddr,0x17); //无限循环,实时监视,直到程序终止。
}
void main()
{
DWORD StartAddr=0;
DWORD EndAddr=0;
DWORD dwCodeLength=0;
DWORD dwChecksum=0;
_asm
{
Start:
xor eax,eax
xor ebx,ebx
inc eax
add ebx,eax
add eax,ebx
mov StartAddr,offset Start
mov EndAddr,offset End
End:
}
dwCodeLength=EndAddr-StartAddr;
Calculation(StartAddr,dwCodeLength);
CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)ThreadStartRoutine,
&StartAddr,0,NULL);//创建一个新的线程,循环监视内存中程序的完整性。
Sleep(88888);
printf("0k!");
}
/***************************************************************************
在C/C++ 代码中实现自我效验,可以使用以下代码段(仅供参考)
_asm
{
LabelStart:
mov dwStartAddr,offset LabelStart
}
。。。。。。
_asm
{
LabelEnd:
mov dwEndAddr,offset LabelEnd
}
***************************************************************************/
在VC中使用内联汇编总是感觉不太直接,所以用MASM 实现之,供喜欢汇编的读者参考。
;####################################
.386
.model flat,stdcall
option casemap:none
;####################################
include windows.inc
include kernel32.inc
include user32.inc
includelib kernel32.lib
includelib user32.lib
;####################################
;###################################################################
.data
;###################################################################
CodeLength=LabelEnd-LabelStart
.code
Calculation proc StartAddr:dword,dwCodeLength:dword
xor eax,eax
xor ebx,ebx
mov esi,StartAddr
mov ecx,dwCodeLength
Label001:
mov bl,byte ptr [esi]
add eax,ebx
inc esi
loop Label001
ret 8
Calculation endp
;###################################################################
CheckSelf proc StartAddr:dword,dwCodeLength:dword,dwChecksum:dword
xor eax,eax
xor ebx,ebx
mov esi,StartAddr
mov ecx,dwCodeLength
Label001:
mov bl,byte ptr [esi]
add eax,ebx
inc esi
loop Label001
cmp eax,dwChecksum
je Label002
call TerminateCurrentProcess
Label002:
ret 0ch
CheckSelf endp
;###################################################################
TerminateCurrentProcess proc
call GetCurrentProcess
push 0
push eax
call TerminateProcess
ret
TerminateCurrentProcess endp
;###################################################################
Start:
LabelStart:
xor eax,eax
inc eax
mov eax,0
sub eax,1
LabelEnd:
push CodeLength
mov eax,offset LabelStart
push eax
call Calculation
push eax
push CodeLength
mov eax,offset LabelStart
push eax
call CheckSelf
end Start
由于本人水平有限,文中疏漏之处在所难免,请读者不吝赐教。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)