首页
社区
课程
招聘
[原创]初探双进程保护
2009-8-5 18:06 43056

[原创]初探双进程保护

2009-8-5 18:06
43056
写一篇关于双进程保护的心得,因为我也是新手兼菜鸟,所以写的比较简单。
windows下一个ring3进程只能对应一个调试器,那么我们就可以预先给自己的应用程序加个调试器,以防止别人跟踪调试。
1.双进程实现:
程序一开始,先判断是否为调试状态,由于刚启动默认为非调试状态,那这样就作为调试器进程运行,作为调试进程运行后,会用自身程序文件再创建一个被调试进程。被调试进程判断出自己处于调试状态,就会沿着不同于调试进程的软件逻辑运行。被调试进程是我们主要运行的进程,而调试进程是用来保护被调试进程的。好像很啰嗦,还是看代码,代码比较简单:
#include "windows.h"

int DebugMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow);

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
  if(!IsDebuggerPresent()) //区分调试进程与被调试进程,以执行不同的代码。
  {
    return DebugMain(hInstance,hPrevInstance,lpCmdLine,nCmdShow); 
  }

  __asm int 3;
  MessageBox(0,"这是一个简单的例子","TraceMe",0);

  return 0;
}

int DebugMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow) //调试进程主函数
{  
  char filename[MAX_PATH];
  GetModuleFileName(0,filename,MAX_PATH); //获取自身文件名
  STARTUPINFO  si={0};
  GetStartupInfo(&si);
  PROCESS_INFORMATION  pi={0};

  if(!CreateProcess(filename,NULL,NULL,NULL,FALSE,DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS,NULL,NULL,&si,&pi)) //创建被调试进程
  {
    return 0; 
  }
  
  BOOL WhileDoFlag=TRUE;
  DEBUG_EVENT DBEvent ;
  DWORD dwState;

  while (WhileDoFlag) 
  {
    WaitForDebugEvent (&DBEvent, INFINITE);
    dwState = DBG_EXCEPTION_NOT_HANDLED ;
    switch (DBEvent.dwDebugEventCode)
    {
      case CREATE_PROCESS_DEBUG_EVENT:
        dwState = DBG_CONTINUE ;
        break;      
        
      case EXIT_PROCESS_DEBUG_EVENT :
        WhileDoFlag=FALSE;
        break ;
      
      case EXCEPTION_DEBUG_EVENT:
        switch (DBEvent.u.Exception.ExceptionRecord.ExceptionCode)
        {
          case EXCEPTION_BREAKPOINT:
          {
            dwState = DBG_CONTINUE ;
            break;
          }
        }
        break;
    }    
    ContinueDebugEvent(pi.dwProcessId, pi.dwThreadId, dwState) ;
  }

  CloseHandle(pi.hProcess) ;
  CloseHandle(pi.hThread)  ;
  return 0;
}

2.SEH
有了自己的调试进程,异常的处理更加灵活了,我们可以将有些异常交给被调试进程自己处理,有些异常交给调试进程处理。总之,为了反跟踪,越乱越好:

__try
{
  __asm int 3  //这个断点异常想让调试进程处理
}
__except(1)
{
  __asm pop eax;  //如果调试器不处理断点异常,把异常扔回来了,这里会被执行,那就做点坏事,这里简单地破坏一下堆栈。
  __asm pop esp;
}

int div=0;
__try
{
  __asm int 3 //这个断点异常想让被调试进程处理
}
__except(1)
{
  div++;
}
div=1/div; //如果被调试进程的异常处理模块未被执行,那么这里会产生除0异常,接着就exit了。  

。。。。。。

case EXCEPTION_BREAKPOINT:  
{
  GetThreadContext(pi.hThread, &Regs) ;
  if(Regs.Eip==(DWORD)0x0040CC10)   //地址需纠正,上面第一个int 3指令的地址+1
    dwState = DBG_CONTINUE ;  
  else if(Regs.Eip==(DWORD)0x0040CC20)  //地址需纠正,上面第二个int 3指令的地址+1
    dwState = DBG_EXCEPTION_NOT_HANDLED ;
  else
    dwState = DBG_CONTINUE ;
  break;
}
。。。。。。

然后在汇编状态下查找两个断点异常的地址,不同编译时,得到的地址值是不同的,记下int 3指令的地址:
0040109C   .  CC            int3
。。。。。。

004010C6   .  CC            int3

根据上面的int 3指令地址纠正地址常数:
0040141C   .  81BD 2CFCFFFF 10CC4000   cmp     dword ptr [ebp-3D4], 0040CC10  ;//将0040CC10改为0040109D
。。。。。。

00401434   >  81BD 2CFCFFFF 20CC4000   cmp     dword ptr [ebp-3D4], 0040CC20  ;//将0040CC20改为004010C7

保存一下,就可以拿OD看一下,稍稍有一点反跟踪效果,可是才两个异常,手工就轻易剔除了。

3.加密代码
做个简单的加密,将代码MessageBox(0,"这是一个简单的例子","TraceMe",0)给加密起来,运行时由调试进程将被调试进程代码还原,有点像smc,不过是跨进程的,该叫什么?代码它(自)修改 ?^_^。先在DebugMain函数中准备代码,因为只是测试,所以用最简单的异或算法:
void DecryptCode(HANDLE hProcess,DWORD begin,DWORD end)
{
  DWORD flOldProtect;
  BYTE ch[1]={0};
  DWORD num=end-begin;
  VirtualProtectEx(hProcess, (LPVOID)begin,num,PAGE_EXECUTE_READWRITE,&flOldProtect);
  for(DWORD i=begin;i<end;i++)
  {
    ReadProcessMemory(hProcess,(LPCVOID)i,&ch,sizeof(ch),NULL) ;
    ch[0]^=0xDE;
    WriteProcessMemory(hProcess,(LPVOID)i,&ch,sizeof(ch),NULL);
  }
  VirtualProtectEx(hProcess,(LPVOID)begin,num,flOldProtect,NULL);
}
。。。。。。

  else if(Regs.Eip==(DWORD)0x0040CC30) //地址值需纠正
  {
    DecryptCode(pi.hProcess,0x0040CC30,0x0041200); //地址值需纠正
    dwState = DBG_CONTINUE;
  }
  else
    dwState = DBG_CONTINUE ;
。。。。。。

接下来就是反汇编状态下,将MessageBox(0,"这是一个简单的例子","TraceMe",0)的二进制数据给COPY出来,然后自己写一段代码加密,将加密后的数据再PASTE回去,最后纠正几个地址值,保存一下就可以了:
首先定位到:
004010F5         .  CC                  int3
004010F6         .  8BF4                mov     esi, esp
004010F8         .  6A 00               push    0                                      ; /Style = MB_OK|MB_APPLMODAL
004010FA         .  68 34004200         push    00420034                               ; |Title = "TraceMe"
004010FF         .  68 1C004200         push    0042001C                               ; |Text = "这是?,BB,"",B8,"黾虻サ睦?,D7,"?
00401104         .  6A 00               push    0                                      ; |hOwner = NULL
00401106         .  FF15 E4524200       call    dword ptr [<&USER32.MessageBoxA>]      ; \MessageBoxA
0040110C            3BF4                cmp     esi, esp
从004010F6到0040110C之前的二进制数据复制出来,这些数据不是固定的,不要随便照搬我这个:
8B F4 6A 00 68 34 00 42 00 68 1C 00 42 00 6A 00 FF 15 E4 52 42 00

加密后再粘贴回去:
55 2A B4 DE B6 EA DE 9C DE B6 C2 DE 9C DE B4 DE 21 CB 3A 8C 9C DE

最后纠正几个地址值:
0040144C      81BD 2CFCFFFF 30CC4000   cmp     dword ptr [ebp-3D4], 0040CC30  ;//0040CC30改为004010F6(第3个int 3指令的地址+1)
00401456      75 25                    jnz     short 0040147D
00401458      68 00124000              push    00401200        ;//00401200改为0040110C(DecryptCode函数实参)
0040145D      68 30CC4000              push    0040CC30        ;//0040CC30改为004010F6(DecryptCode函数实参)


总算打完了,我也是初学者,欢迎大家能给予指点。

[培训]《安卓高级研修班(网课)》月薪三万计划,掌 握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

上传的附件:
收藏
点赞7
打赏
分享
最新回复 (34)
雪    币: 230
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
fkeuaii 2009-8-5 18:55
2
0
虽然不是很明白,但是还是谢谢楼主
雪    币: 1450
活跃值: (35)
能力值: (RANK:680 )
在线值:
发帖
回帖
粉丝
jackozoo 14 2009-8-5 19:18
3
0
标题不大合适.

简单的Debug API应用.

更经典的例子见riijj版主的一个CM.
雪    币: 97
活跃值: (30)
能力值: ( LV2,RANK:140 )
在线值:
发帖
回帖
粉丝
DebugFan 3 2009-8-5 20:23
4
0
标题是有点不合适,双进程保护本是指用来保护进程不死的,我这个标题会混淆概念,呵呵。

基本上大家都看过《加密与解密3》15.3.8 双进程保护,我这个概念跟它差不多,反正《加密与解密3》这么出名,就先将就一下了。
雪    币: 97
活跃值: (30)
能力值: ( LV2,RANK:140 )
在线值:
发帖
回帖
粉丝
DebugFan 3 2009-8-5 20:50
5
0
雪    币: 264
活跃值: (11)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
himcrack 6 2009-8-6 00:02
6
0
双进程 很好的资料 仔细拜读..
雪    币: 62
活跃值: (72)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
creakerzgz 1 2009-8-6 00:08
7
0
学习之
雪    币: 191
活跃值: (335)
能力值: ( LV9,RANK:450 )
在线值:
发帖
回帖
粉丝
RegKiller 10 2009-8-6 01:01
8
0
学习了,标记一下。
雪    币: 7300
活跃值: (3758)
能力值: (RANK:1130 )
在线值:
发帖
回帖
粉丝
海风月影 22 2009-8-6 09:57
9
0
运行一遍,dump一下,代码就全出来了
雪    币: 97
活跃值: (30)
能力值: ( LV2,RANK:140 )
在线值:
发帖
回帖
粉丝
DebugFan 3 2009-8-6 10:41
10
0
厉害!

得在被调试进程中多加几个断点,然后由调试进程把被调试进程运行过的代码一阵乱改.
雪    币: 264
活跃值: (11)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
himcrack 6 2009-8-6 14:05
11
0
运行完之后断点 抹掉代码
雪    币: 219
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xuwg 2009-8-6 14:15
12
0
我是来学习的!
雪    币: 474
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
feierin 2009-8-6 14:16
13
0
谢谢楼主
学习了
雪    币: 7300
活跃值: (3758)
能力值: (RANK:1130 )
在线值:
发帖
回帖
粉丝
海风月影 22 2009-8-7 14:39
14
0
父进程是可以调试的,不是说抹就抹的
雪    币: 202
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
暗夜之神 2009-8-12 16:52
15
0
我是初学者
看来对这方面要好好的研究下了
谢谢楼主提供的思路
雪    币: 199
活跃值: (72)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
liuheqiang 2 2009-8-12 17:02
16
0
太难的东西只能看看
雪    币: 264
活跃值: (11)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
himcrack 6 2009-8-12 18:10
17
0
相当于在调试一个调试器??那怎么办呢?类似于safedisc创建调试进程?
雪    币: 97
活跃值: (30)
能力值: ( LV2,RANK:140 )
在线值:
发帖
回帖
粉丝
DebugFan 3 2009-8-15 20:31
18
0
贴一点关于双进程反跟踪与vm结合的简单测试代码:

///////////////// vm.h /////////////////

typedef struct _tagVMCONTEXT
{
	DWORD eax;
	DWORD ebx;
	DWORD ecx;
	DWORD edx;
	DWORD edi;
	DWORD esi;
	DWORD ebp;
	DWORD *esp;
	DWORD eflags;
	BYTE arg[5]; //操作数
	DWORD stack[4096];  //堆栈
} VMCONTEXT;

VMCONTEXT vmcontext;
typedef int (* VMHANDLER)(void);

__declspec(naked) int VMStart()
{
	__asm
	{
		push eax
		pop [vmcontext.eax]
		push ebx
		pop [vmcontext.ebx]
		push ecx
		pop [vmcontext.ecx]
		push edx
		pop [vmcontext.edx]
		push edi
		pop [vmcontext.edi]
		push esi
		pop [vmcontext.esi]
		push ebp
		pop [vmcontext.ebp]
		pushfd
		pop [vmcontext.eflags]
		push eax
		mov eax,offset vmcontext.stack
		add eax,0x1000*4-4
		push eax
		pop [vmcontext.esp]
		pop eax
		mov eax,1     //返回值为1
		ret
	}
}

__declspec(naked) int VMExit()
{
	__asm
	{
		push [vmcontext.eax]
		pop eax
		push [vmcontext.ebx]
		pop ebx
		push [vmcontext.ecx]
		pop ecx
		push [vmcontext.edx]
		pop edx
		push [vmcontext.edi]
		pop edi
		push [vmcontext.esi]
		pop esi
		push [vmcontext.ebp]
		pop ebp
		push [vmcontext.eflags]
		popfd
		mov eax,1   //返回值为1
		ret
	}
}


int VPushImm32()
{
	DWORD imm=*((DWORD *)vmcontext.arg);
	vmcontext.esp--;
	*vmcontext.esp=imm;
	return 5;  //返回值 = 1 + 用到的操作数个数,下同。
}

int VCallImm32()
{
	DWORD imm =*((DWORD *)vmcontext.arg);
	_asm
	{
		mov esi, esp
		mov esp, [vmcontext.esp]
		call imm
		push eax
		pop [vmcontext.eax]
		mov esp, esi
	}
	return 5;
}

int VMovReg32Mem32()
{
	BYTE index=vmcontext.arg[0];
	DWORD *addr =*(DWORD **)(&vmcontext.arg[1]);
	((DWORD *)&vmcontext)[index]=*addr;
	return 6;
}

int VCmpReg32Imm32()
{
	BYTE index=vmcontext.arg[0];
	DWORD reg=((DWORD *)&vmcontext)[index];
	DWORD imm=*((DWORD*)(&vmcontext.arg[1]));
	if(reg==imm)
	{
		vmcontext.eflags = vmcontext.eflags | 0x40;  //ZF标志置1
	}
	else
	{
		vmcontext.eflags = vmcontext.eflags & 0xFFFFFFBF; //ZF标志置0
	}
	return 6;
}

int VJnz()
{
	int iRtn;
	if(vmcontext.eflags & 0x40)
	{
		iRtn = 5;
	}
	else
	{
		iRtn = *((int *)vmcontext.arg);
	}
	return iRtn;
}

int VJmp()
{
	int iRtn=*((int *)vmcontext.arg);
	return iRtn;
}

int VNop()
{
	return 1;
}

int VPop()
{
	vmcontext.esp++;
	return 1;
}

VMHANDLER VMHandlerTable[]= //删减了许多Handler,这里相当的不完整
{
	(VMHANDLER)NULL, //00,无对应Handler
	(VMHANDLER)VMStart, //01
	(VMHANDLER)VMExit,  //02

	(VMHANDLER)VNop, //03
	(VMHANDLER)VMovReg32Mem32, //04
	(VMHANDLER)VCmpReg32Imm32, //05

	(VMHANDLER)VPushImm32, //06
	(VMHANDLER)VCallImm32, //07
	(VMHANDLER)VPop, //08

	(VMHANDLER)VJnz,//09
	(VMHANDLER)VJmp, //0A
};

///////////////// vm.cpp /////////////////

#define _WIN32_WINNT 0x0500
#include "windows.h"
#include "iostream.h"
#include "vm.h"

int DebugMain(int argc, char* argv[]);

DWORD key;
char w[]="wrong";
char r[]="right";

void ShowMessage(char *s)
{
	cout<<s;
}


int main(int argc, char* argv[])
{
	if(!IsDebuggerPresent())
	{
		return DebugMain(argc,argv); 
	}
	cin>>key;
	__asm int 3;
	return 0;
}

int DebugMain(int argc, char* argv[])
{
	char filename[260];
	GetModuleFileName(0,filename,260); //获取自身文件名
	STARTUPINFO	si={0};
	GetStartupInfo(&si);
	PROCESS_INFORMATION	pi={0};
	if(!CreateProcess(filename,NULL,NULL,NULL,FALSE,DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS,NULL,NULL,&si,&pi)) //创建被调试进程
	{
		return 0; 
	}

	BOOL WhileDoFlag=TRUE;
	DEBUG_EVENT DBEvent ;
	DWORD dwState;
	CONTEXT Regs ;
	Regs.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;

	BYTE vm_code[20][6]=
	{
		0x00,0x00,0x01,0x03,0x03,0x03, //VMStart
		0x04,0x00,0x00,0x00,0x00,0x00, //VMovReg32Mem32
		0x05,0x00,0x78,0x56,0x34,0x12, //VCmpReg32Imm32
		0x09,0x12,0x00,0x00,0x00,0x03, //VJnz
		0x06,0x00,0x00,0x00,0x00,0x03, //VPushImm32 
		0x0A,0x0C,0x00,0x00,0x00,0x03, //VJmp
		0x06,0x00,0x00,0x00,0x00,0x03, //VPushImm32 
		0x07,0x00,0x00,0x00,0x00,0x03, //VCallImm32
		0x08,0x03,0x03,0x03,0x03,0x03, //VPop	
		0x02,0x00,0x00,0x00,0x00,0x00, //VMExit
	}; 
	BYTE *vm_eip=&vm_code[0][0];

	*((DWORD *)(&vm_code[1][2]))=(DWORD)&key;
	*((DWORD *)(&vm_code[4][1]))=(DWORD)r;
	*((DWORD *)(&vm_code[6][1]))=(DWORD)w;
	*((DWORD *)(&vm_code[7][1]))=(DWORD)ShowMessage;

	while (WhileDoFlag) 
	{
		WaitForDebugEvent (&DBEvent, INFINITE);
		dwState = DBG_EXCEPTION_NOT_HANDLED ;
		switch (DBEvent.dwDebugEventCode)
		{
			case CREATE_PROCESS_DEBUG_EVENT:
				dwState = DBG_CONTINUE ;
				break;			
				
			case EXIT_PROCESS_DEBUG_EVENT :
				WhileDoFlag=FALSE;
				break ;
			
			case EXCEPTION_DEBUG_EVENT:
				switch (DBEvent.u.Exception.ExceptionRecord.ExceptionCode)
				{
					case EXCEPTION_BREAKPOINT: //下面相当于VM的调度器
					{
						GetThreadContext(pi.hThread, &Regs);
						if(*vm_eip!=NULL)
						{
							vm_eip+=Regs.Eax;
						}
						else
						{
							vm_eip+=1;
						}
						if(*vm_eip!=NULL)
						{
							DWORD addrCC=(--Regs.Eip); //int 3指令地址
							Regs.Esp-=4;
							WriteProcessMemory(pi.hProcess,(LPVOID)Regs.Esp,&addrCC,sizeof(addrCC),NULL); //将int 3指令地址放入堆栈。
							Regs.Eip=(DWORD)VMHandlerTable[*vm_eip]; //修改eip为handler地址
							WriteProcessMemory(pi.hProcess,(LPVOID)vmcontext.arg,vm_eip+1,5,NULL); //传入参数
							SetThreadContext(pi.hThread,&Regs);
						}
						dwState = DBG_CONTINUE ;
						break;
					}
				}
				break;
		}		
		ContinueDebugEvent(pi.dwProcessId, pi.dwThreadId, dwState) ;
	}

	CloseHandle(pi.hProcess) ;
	CloseHandle(pi.hThread)  ;
	return 0;
}

实际上就是将VM的Handler留在子进程中执行,而将VM的调度器和伪代码放在父进程的执行代码中,在父进程的控制下,实现vm在双进程之间的"来回切换"(表达不是很确切),从而增加逆向跟踪的难度。我贴出来的只是一个简单的测试代码,还缺少很多很多东西,感兴趣的朋友可以去实现一个完整的双进程vm模型。

ps:参考了Bughoho版主的虚拟机源代码。
雪    币: 4476
活跃值: (2859)
能力值: ( LV13,RANK:283 )
在线值:
发帖
回帖
粉丝
littlewisp 2 2009-8-15 21:49
19
0
向楼主学习.
雪    币: 1450
活跃值: (35)
能力值: (RANK:680 )
在线值:
发帖
回帖
粉丝
jackozoo 14 2009-8-15 22:19
20
0
18L的思路不错, good  

若要更WS的话,  可以设计成3进程, 1和2 负责两套不同VM指令系统. 3中放第二套VM指令系统的Handlers.
雪    币: 97
活跃值: (30)
能力值: ( LV2,RANK:140 )
在线值:
发帖
回帖
粉丝
DebugFan 3 2009-8-15 23:03
21
0
这样也太。。。
雪    币: 290
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
狼行wolf 2009-8-16 06:53
22
0
看id 看成debugman了。
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
gzyg 2009-8-18 16:46
23
0
看 不 懂    呵呵
雪    币: 203
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ljlLionel 2009-8-22 10:44
24
0
下载下来看看
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zgjn 2009-8-22 13:26
25
0
学习了,谢谢楼主
游客
登录 | 注册 方可回帖
返回