前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >通过LUMP_PAKFILE的源引擎内存损坏

通过LUMP_PAKFILE的源引擎内存损坏

原创
作者头像
franket
发布2020-10-10 19:12:21
1.9K0
发布2020-10-10 19:12:21
举报
文章被收录于专栏:技术杂记技术杂记技术杂记

一个月前,我在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提供的堆栈跟踪,以确定发生了什么。

WinDBG堆栈跟踪
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文件,您将看到以下内容。

010编辑器将LUMP_PAKFILE作为Zipfile查看
010编辑器将LUMP_PAKFILE作为Zipfile查看

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提供的堆栈跟踪,我们会看到很多相同的东西。

WinDBG堆栈跟踪
WinDBG堆栈跟踪

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

m_PutOverflowFunc
m_PutOverflowFunc

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
目录
  • 历史
    • 错误
      • 最小化
        • 符号和来源(代码)
        相关产品与服务
        图数据库 KonisGraph
        图数据库 KonisGraph(TencentDB for KonisGraph)是一种云端图数据库服务,基于腾讯在海量图数据上的实践经验,提供一站式海量图数据存储、管理、实时查询、计算、可视化分析能力;KonisGraph 支持属性图模型和 TinkerPop Gremlin 查询语言,能够帮助用户快速完成对图数据的建模、查询和可视化分析。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档