顺便聊一下通过LUMP_PAKFILE的源引擎内存损坏

一个月前,我在Twitter上放了一个零日的Source引擎,而对其功能没有太多解释。确定不幸的是无法利用之后,我们将对其进行探索,并探索一下Valve的Source Engine。

历史

Valve的Source Engine最初于2004年6月发布,第一款使用该引擎的游戏是Counter-Strike:Source,它于2004年11月1日(大约15年前)发布了自己。尽管被吹捧为“完全重写”,但Source仍然从GoldSrc及其父Quake Engine继承代码。除了可能从GoldSrc和Quake(GoldSrc本身就是受害者)中窃取错误之外,Valve引擎的安全模型还不存在。Valve尚未成为今天的强大力量,但是我们还留下了许多愚蠢的错误,老兄,包括设计自己的内存分配器(或更确切地说,做一个包装器malloc)。

值得注意的是,游戏开发自己的分配器是相对常见的,但是从安全性的角度来看,它仍然不是最大的分配器。

错误

A47B98我释放的.bsp文件中偏移量的字节,以及\x90\x90\x90\x90解析为的以下三个字节(),UInt32控制着加载.bsp时(即CS:GO中)分配了多少内存(尽管也会影响CS:S,TF2 ,以及L4D2)。这就是它的不足。

要了解更多,我们将不得不更深入地研究。最近,大约在2017年的《九头蛇》行动的CS:GO的源代码发布了-这将是我们的主要工具。

让我们从WinDBG开始。csgo.exe加载了参数后-safe -novid -nosound +map exploit.bsp,我们在“ Host_NewGame”上遇到了第一个偶然的异常。

---- Host_NewGame ----
(311c.4ab0): Break instruction exception - code 80000003 (first chance)
*** WARNING: Unable to verify checksum for C:\Users\triaz\Desktop\game\bin\tier0.dll
eax=00000001 ebx=00000000 ecx=7b324750 edx=00000000 esi=90909090 edi=7b324750
eip=7b2dd35c esp=012fcd68 ebp=012fce6c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
tier0!CStdMemAlloc::SetCRTAllocFailed+0x1c:
7b2dd35c cc              int     3

在寄存器上,$esi我们可以看到四个负责字节,如果我们查看一下堆栈指针,

出于简洁起见,已删除了完整的堆栈跟踪。

00 012fce6c 7b2dac51 90909090 90909090 012fd0c0 tier0!CStdMemAlloc::SetCRTAllocFailed+0x1c [cstrike15_src\tier0\memstd.cpp @ 2880] 
01 (Inline) -------- -------- -------- -------- tier0!CStdMemAlloc::InternalAlloc+0x12c [cstrike15_src\tier0\memstd.cpp @ 2043] 
02 012fce84 77643546 00000000 00000000 00000000 tier0!CStdMemAlloc::Alloc+0x131 [cstrike15_src\tier0\memstd.cpp @ 2237] 
03 (Inline) -------- -------- -------- -------- filesystem_stdio!IMemAlloc::IndirectAlloc+0x8 [cstrike15_src\public\tier0\memalloc.h @ 135] 
04 (Inline) -------- -------- -------- -------- filesystem_stdio!MemAlloc_Alloc+0xd [cstrike15_src\public\tier0\memalloc.h @ 258] 
05 (Inline) -------- -------- -------- -------- filesystem_stdio!CUtlMemory<unsigned char,int>::Init+0x44 [cstrike15_src\public\tier1\utlmemory.h @ 502] 
06 012fce98 7762c6ee 00000000 90909090 00000000 filesystem_stdio!CUtlBuffer::CUtlBuffer+0x66 [cstrike15_src\tier1\utlbuffer.cpp @ 201]

或者,以更简洁的形式-

0:000> dds esp
012fcd68  90909090

的字节$esi直接在堆栈指针(duh)上。美好的开始。请记住该模块-filesystem_stdio稍后将很重要。如果我们继续调试-

***** OUT OF MEMORY! attempted allocation size: 2425393296 ****
(311c.4ab0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000032 ebx=03128f00 ecx=012fd0c0 edx=00000001 esi=012fd0c0 edi=00000000
eip=00000032 esp=012fce7c ebp=012fce88 iopl=0         nv up ei ng nz ac po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010292
00000032 ??              ???

然后我们看到了-内存分配器尝试分配0x90909090as UInt32。现在,尽管我只是简单地使用HxD对此进行了验证,但是以下Python 2.7单行代码也应该起作用。

print int('0x90909090', 0)

(对于Python 3,您必须从int该行的开始将所有内容封装在另一组括号中。RTFM。)

它将返回2425393296,尝试分配Source的意大利面条代码的值。(在内部,Python的int整数处理方式似乎与ctypes.c_uint32-相同,为简单起见,我们使用int,但是您可以轻松地import ctypes复制该发现。可能要使用2.7来完成,因为3处理字符,字节,等等。)

因此,让我们更深入地研究吧?下一部分我们会使用macOS,是喜欢它还是讨厌它,因为每个为该平台编写跨平台代码的人(通常是达尔文)似乎都忘记了剥离二进制文件是一件事情-我们没有用于NT,因此macOS应该是可行的替代品-但是,嘿,我们拥有该死的源代码,因此我们可以在Windows上执行此操作。

最小化

在我们充分利用漏洞之前,要做的一件事是最大程度地减少漏洞。该错误是zzuf使用CERT的BFF工具重新发现包装后发现的。如果我们查看原始地图(cs_assault)与我们的原始地图之间的差异,我们可以看到差异很大。

在这种情况下,使用BSPInfo手动最小化,并提取并比较块。不出所料,关键错误出现在块40中-LUMP_PAKFILE。该块实际上是一个大的.zip文件。我们可以使用010编辑器的ZIP文件模板进行检查。

符号和来源(代码)

Steam释放和泄漏源之间的行为有很大不同。

没有错误会在各个平台上以完全相同的方式起作用。假定您的目标是武器化,或者甚至从Valve在H1上获得最大收益,您的主要目标应该是Win32-尽管其他平台是可行的替代方案。Linux提供了一些出色的工具,Valve经常忘记strip在macOS上发生了一件事情(许多其他开发人员也是如此)。

我们可以查看WinDBG提供的堆栈跟踪,以确定发生了什么。

从第8帧开始,我们将逐步进行。

每个片段的第一行将指示WinDBG决定问题所在的位置。

		if ( pf->Prepare( packfile->filelen, packfile->fileofs ) )
		{
			int nIndex;
			if ( addType == PATH_ADD_TO_TAIL )
			{
				nIndex = m_SearchPaths.AddToTail();	
			}
			else
			{
				nIndex = m_SearchPaths.AddToHead();	
			}

			CSearchPath *sp = &m_SearchPaths[ nIndex ];

			sp->SetPackFile( pf );
			sp->m_storeId = g_iNextSearchPathID++;
			sp->SetPath( g_PathIDTable.AddString( newPath ) );
			sp->m_pPathIDInfo = FindOrAddPathIDInfo( g_PathIDTable.AddString( pPathID ), -1 );

			if ( IsDvdDevPathString( newPath ) )
			{
				sp->m_bIsDvdDevPath = true;
			}

			pf->SetPath( sp->GetPath() );
			pf->m_lPackFileTime = GetFileTime( newPath );

			Trace_FClose( pf->m_hPackFileHandleFS );
			pf->m_hPackFileHandleFS = NULL;

			//pf->m_PackFileID = m_FileTracker2.NotePackFileOpened( pPath, pPathID, packfile->filelen );
			m_ZipFiles.AddToTail( pf );
		}
		else
		{
			delete pf;
		}
	}
}

值得注意的是,您已正确阅读此文件-LUMP_PAKFILE只是嵌入式ZIP文件。这里没有太多的后果-指出m_ZipFiles确实确实是指熟悉的档案格式。

第7帧是我们开始观察发生了什么的地方。

	zipDirBuff.EnsureCapacity( rec.centralDirectorySize );
	zipDirBuff.ActivateByteSwapping( IsX360() || IsPS3() );
	ReadFromPack( -1, zipDirBuff.Base(), -1, rec.centralDirectorySize, rec.startOfCentralDirOffset );
	zipDirBuff.SeekPut( CUtlBuffer::SEEK_HEAD, rec.centralDirectorySize );

如果要在010 Editor中打开LUMP_PAKFILE并将其解析为ZIP文件,您将看到以下内容。

elDirectorySizerec.centralDirectorySize在这种情况下是我们的。向前跳一帧,我们可以看到以下内容。

注释行突出显示了感兴趣的行。

CUtlBuffer::CUtlBuffer( int growSize, int initSize, int nFlags ) : 
	m_Error(0)
{
	MEM_ALLOC_CREDIT();
	m_Memory.Init( growSize, initSize );
	m_Get = 0;
	m_Put = 0;
	m_nTab = 0;
	m_nOffset = 0;
	m_Flags = nFlags;
	if ( (initSize != 0) && !IsReadOnly() )
	{
		m_nMaxPut = -1;
		AddNullTermination( m_Put );
	}
	else
	{
		m_nMaxPut = 0;
	}
	...

接下来是下一帧

template< class T, class I >
void CUtlMemory<T,I>::Init( int nGrowSize /*= 0*/, int nInitSize /*= 0*/ )
{
	Purge();

	m_nGrowSize = nGrowSize;
	m_nAllocationCount = nInitSize;
	ValidateGrowSize();
	Assert( nGrowSize >= 0 );
	if (m_nAllocationCount)
	{
		UTLMEMORY_TRACK_ALLOC();
		MEM_ALLOC_CREDIT_CLASS();
		m_pMemory = (T*)malloc( m_nAllocationCount * sizeof(T) );
	}
}

最后,

inline void *MemAlloc_Alloc( size_t nSize )
{ 
	return g_pMemAlloc->IndirectAlloc( nSize );
}

nSize我们控制的值在哪里,或者$esi。请记住,这一切都是在实际的段错误和$eip腐败发生之前进行的。向前跳–

***** OUT OF MEMORY! attempted allocation size: 2425393296 ****
(311c.4ab0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000032 ebx=03128f00 ecx=012fd0c0 edx=00000001 esi=012fd0c0 edi=00000000
eip=00000032 esp=012fce7c ebp=012fce88 iopl=0         nv up ei ng nz ac po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010292
00000032 ??              ???

我们也遇到了同样的错误。值得注意的是$eax$eip具有相同的值,并且在整个运行过程中保持一致。如果我们看一下WinDBG提供的堆栈跟踪,我们会看到很多相同的东西。

从中挑选当地人CZipPackFile::Prepare,我们可以看到上的值,$eip$eax重复了几次。即,元组m_PutOverflowFunc

因此,我们能够破坏此变量,从而破坏控制权$eax$eip但不幸的是,破坏程度不大。基于游戏版本和地图数据,这些值或多或少似乎是任意的。本质上,我们拥有的是一个具有nSize(0x90909090)值的malloc,可以完全控制该变量nSize。但是,它不会检查它是否返回有效的指针–因此,当我们尝试分配2 GB的内存(并返回零)时,游戏只会出现段错误。最后,我们得到了一种新颖的拒绝服务,其结果是在指令指针的“控制”中-尽管在某种程度上我们不能弹出外壳,计算或对其进行任何有趣的操作。

正文完