看流星社区

 找回密码
 注册账号
查看: 1997|回复: 0

内核态下基于动态感染技术的应用程序执行保护(五 动态感染)

[复制链接]

该用户从未签到

发表于 2017-6-1 17:26:24 | 显示全部楼层 |阅读模式
分类:技术汇编

源代码:http://download.csdn.net/detail/hitetoshi/3633188
绝影做尘,铁甲四方逐鹿而争。风起云涌,八面九锡更兼策马奔。南面独傲,铜雀二乔,欲休却报诏到。假节钺,赞拜不名,剑履入殿谁能?青梅煮酒,本初不数,惊起雷霆咋休。玺绶之册,即阼亦重,却累身三秋。观之沧海,星汉一粟,不道身成五彩。隔江叹:景升之子,豚犬之流。

扯远了,赶紧返回。今天这章是这篇系列文章的最后一章。
前面我们的驱动程序已经可以监视进程的创建,下面我们希望做的事情有两点:一、判断进程是不是notepad.exe。二、如果是,我们向其进程注入一段代码并先于它原有的代码执行。所以,这里有三个重要的问题:一、从进程句柄获取进程名;二、向进程分配内存写入自定位的代码;三、修改进程原来的入口点,改为我们代码的入口点,在我们的代码执行完毕后,还得跳转到原来的入口点去。这整个过程本质上与文件感染没多少不同。
如何从进程句柄获取到进程名呢?答案是EPROCESS,ObReferenceObjectByHandle可以让我们通过进程句柄获取它的EPROCESS。微软在WDK中对EPROCESS的说明非常简短:The EPROCESS structure is an opaque structure that serves as the process object for a process.EPROCESS结构中保存有进程名,但不幸的是EPROCESS结构的定义随操作系统的不同而不同,这也许也是为什么微软对它的描述非常少,他也不推荐你使用EPROCESS结构。
在KmdKit中有个SharedEvent – ProcessMon例子,其中演示了通过EPROCESS获取进程名,它的做法是针对不同的系统定义不同的EPROCESS结构,在使用时先获取当前系统。
在《Windows内核安全编程》中提到了另外一种方法:当我们的内核模块DriverEntry被调用时,我们的内核正处于System进程中,我们可以使用PsGetCurrentProcess获取到此时的EPROCESS,在其中暴力搜索“System”,如果搜索出来,我们就可以确定本系统中进程名相对于EPROCESS首地址的偏移,以后就可以用这个偏移加别的进程的EPROCESS首地址来获取别的进程的名了。
后者显然要简洁得多,所以本文也采用了后者的办法。在DriverEntry中增加:
invoke GetNameOffset
mov g_uNameOffset,eax
invoke DbgPrint,$CTA0("Driver entry, name offset:%08X"),g_uNameOffset

GetNameOffset proc uses ebx
local pProcess
local len
local dwOffset

and dwOffset,0
invoke PsGetCurrentProcess
.if eax
mov pProcess,eax
invoke strlen,$CTA0("System")
mov len,eax
xor ebx,ebx
.while ebx<1024*3*4
mov eax,pProcess
add eax,ebx
invoke _strnicmp,$CTA0("System"),eax,len
.if !eax
mov dwOffset,ebx
.break
.endif
inc ebx
.endw
.endif

mov eax,dwOffset
ret
GetNameOffset endp
这个问题解决了,我们先把HookProc.asm中Hook_NtCreateThread代码贴出来:

include Append.asm

Hook_NtCreateThread proc ThreadHandleHANDLE,DesiredAccessWORD,ObjectAttributesOBJECT_ATTRIBUTES,ProcessHandle:HANDLE,ClientIdCLIENT_ID,ThreadContextCONTEXT,InitialTebVOID,CreateSuspendedWORD
local pProcessVOID
local ulEntryPoint:ULONG
local dwAllocationSizeWORD
local pBaseAddressVOID
local pProcessName

local dwMemorySizeWORD
local pAppendStartVOID
local pOldEntryVOID
local pAppendEntryVOID

pushad
pushfd
.if ThreadContext&amp;&amp;CreateSuspended&amp;&amprocessHandle&amp;&amprocessHandle!=-1
invoke ObReferenceObjectByHandle,ProcessHandle,PROCESS_ALL_ACCESS,NULL,UserMode,addr pProcess,NULL
.if eax==STATUS_SUCCESS
.if g_uNameOffset
mov eax,pProcess
add eax,g_uNameOffset
mov pProcessName,eax
invoke DbgPrint,$CTA0("New process:%s"),pProcessName

invoke _stricmp,pProcessName,$CTA0("notepad.exe")
.if !eax
mov dwMemorySize,APPEND_CODE_LENGTH
mov pAppendStart,offset APPEND_CODE_START
mov pOldEntry,offset _ulOldEntry
mov pAppendEntry,offset _AppendCodeEntry
mov edi,ThreadContext
assume edi:ptr CONTEXT
M2M ulEntryPoint,[edi].regEax ;保存入口点
.if ulEntryPoint
invoke KeDetachProcess
invoke KeAttachProcess,pProcess
;分配内存
M2M dwAllocationSize,dwMemorySize
mov pBaseAddress,NULL
invoke ZwAllocateVirtualMemory,NtCurrentProcess,addr pBaseAddress,0,addr dwAllocationSize,MEM_COMMIT,PAGE_EXECUTE_READWRITE
.if eax==STATUS_SUCCESS
;写入代码
invoke memcpy,pBaseAddress,pAppendStart,dwMemorySize
;填写Jmp地址
mov eax,pOldEntry
sub eax,pAppendStart
add eax,pBaseAddress
M2M dword ptr [eax],ulEntryPoint
;修正入口点
mov eax,pAppendEntry
sub eax,pAppendStart
add eax,pBaseAddress
mov ulEntryPoint,eax
.endif
invoke KeDetachProcess
M2M [edi].regEax,ulEntryPoint

.endif
.endif
.endif

assume edi:nothing
invoke ObDereferenceObject,pProcess
.endif
.endif
popfd
popad

invoke g_lpOldNtCreateThread,ThreadHandle,DesiredAccess,ObjectAttributes,ProcessHandle,ClientId,ThreadContext,InitialTeb,CreateSuspended

ret
Hook_NtCreateThread endp

Append.asm中是我们要感染的代码,等会来看。进程创建时主线程也会被创建。但在我们的Hook中,线程还没有开始运行。其中ThreadContext(这个结构跟用户态下用GetThreadContext获取到的差不多)的regEax就是主线程的起始地址,也就是我们常说的入口点。代码的思路非常简单,在目标进程中分配一块APPEND_CODE_LENGTH大小的内存(这个大小刚好够装下我们需要注入的代码);把我们程序中APPEND_CODE_START地址拷贝那么多数据过去(这其实就是把我们的感染代码拷贝过去;把进程原来的入口点保存到拷贝过去的那份代码的某个位置(具体那个位置看Append.asm就明白了),吧ThreadContext.regEax设置为我们注入代码的起始地址。

这下来看Append.asm

APPEND_CODE_START equ this byte

include proto.inc
include Append.inc

_ulOldEntry dd ?
_hKernel32 dd ?
_hUser32 dd ?

_szUser32 db 'User32.dll',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_lpGetProcAddress APIGetProcAddress ?
_lpGetModuleHandleA APIGetModuleHandle ?
_lpLoadLibraryA APILoadLibrary ?
;-------------------------------------------------------------------------------
_szGetProcAddress db 'GetProcAddress',0
_szGetModuleHandleA db 'GetModuleHandleA',0
_szLoadLibraryA db 'LoadLibraryA',0,0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_lpMessageBoxA APIMessageBox ?
;------------------------------------------------------------------------------
_szMessageBoxA db 'MessageBoxA',0,0

_lpTitle db 'DynamicHook',0
_lpMsg db 'My append code!',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include GetKernel.asm
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_AppendCodeEntry:
call @F
@@:
pop ebx
sub ebx,offset @B

.if dword ptr [esp]>7FFFFFFFh
jmp OLD_ENTRY
.endif
invoke _GetKernelBase,[esp]
.if !eax
jmp OLD_ENTRY
.endif
mov [ebx+_hKernel32],eax

invoke _GetApi,[ebx+_hKernel32],addr [ebx+offset _szGetProcAddress]
.if !eax
jmp OLD_ENTRY
.endif
mov [ebx+_lpGetProcAddress],eax

lea esi,[ebx+offset _szGetModuleHandleA]
lea edi,[ebx+offset _lpGetModuleHandleA]
.while TRUE
invoke [ebx+_lpGetProcAddress],[ebx+_hKernel32],esi
.if !eax
jmp OLD_ENTRY
.endif
mov [edi],eax
add edi,4
@@:
lodsb
or al,al
jnz @B
.break .if ! byte ptr [esi]
.endw

invoke [ebx+_lpGetModuleHandleA],addr [ebx+_szUser32]
.if !eax
invoke [ebx+_lpLoadLibraryA],addr [ebx+_szUser32]
.if !eax
jmp OLD_ENTRY
.endif
.endif
mov [ebx+_hUser32],eax
lea esi,[ebx+offset _szMessageBoxA]
lea edi,[ebx+offset _lpMessageBoxA]
.while TRUE
invoke [ebx+_lpGetProcAddress],[ebx+_hUser32],esi
.if !eax
jmp OLD_ENTRY
.endif
mov [edi],eax
add edi,4
@@:
lodsb
or al,al
jnz @B
.break .if ! byte ptr [esi]
.endw

invoke [ebx+_lpMessageBoxA],NULL,addr [ebx+_lpMsg],addr [ebx+_lpTitle],0

OLD_ENTRY:
jmp [ebx+_ulOldEntry]

APPEND_CODE_END equ this byte
APPEND_CODE_LENGTH equ offset APPEND_CODE_END-offset APPEND_CODE_START
这下大家明白APPEND_CODE_LENGTH、APPEND_CODE_START、_AppendCodeEntry、_ulOldEntry的含义了吧。在这里汇编语言的好处就显而易见了:可以精确获取指定代码段的长度(你若用高级语言的话,就得估摸着分配一块足够大的内存);可以使用相对于ShellCode来说稍微高级一点的语言写代码(ShellCode要想完成复杂一点的功能还相当麻烦呢)。
这里要注意的是,首先对所有用户态API的调用我们都不能直接调用,一是内核的导入库中根本没有提供这些函数,二是我们访问全局变量不能直接来访问,因为随着进程不同,我们注入到目标进程的代码起始地址也是不同的(分配的pBaseAddress不同)。这需要我们手动加载需要的DLL,获取API函数地址,并且编写自定位代码。这些技术早几年前就已经科普,这里就不再科普了。
GetKernel.asm中的_GetKernelBase用来获取kernel32.dll的基址。方法也有很多,这里直接用的《Windows环境下32位会变语言程序设计》中的代码。
现在来试运行一下:

哈哈!当我们启动notepad.exe时首先会弹出我们的对话框。如果你用OllyDBG调试一下notepad.exe,选“设置第一次暂停于:主模块入口点”,你会发现,当OllyDBG中断时,我们的代码已经执行过了。notepad.exe根本就没有机会知道我们的代码已经影响了它。
但是!任何事情就怕但是!notepad.exe在后面还有机会来扫描内存…….如果我们在
OLD_ENTRY:
jmp [ebx+_ulOldEntry]
处用下面的办法呢?(伪代码)
push 8000
push APPEND_CODE_LENGTH
push offset [ebx+APPEND_CODE_START]
push dword ptr [ebx+_ulOldEntry]
jmp [ebx+_lpVirtualFree]

这种动态感染的办法后面还有后话,我们的代码虽然很早就执行了,但系统创建进程时,还得先运行很多代码,OllyDBG仍然可以中断在系统断点上。可是,我们也不应该把别人憋死。给别人留条活路就是给自己留条活路。
点击按钮快速添加回复内容: 支持 高兴 激动 给力 加油 苦寻 生气 回帖 路过 感恩
您需要登录后才可以回帖 登录 | 注册账号

本版积分规则

小黑屋|手机版|Archiver|看流星社区 |网站地图

GMT+8, 2024-3-19 19:43

Powered by Kanliuxing X3.4

© 2010-2019 kanliuxing.com

快速回复 返回顶部 返回列表