7

渗透技巧——"隐藏"注册表的更多测试

 3 years ago
source link: https://3gstudent.github.io/3gstudent.github.io/%E6%B8%97%E9%80%8F%E6%8A%80%E5%B7%A7-%E9%9A%90%E8%97%8F-%E6%B3%A8%E5%86%8C%E8%A1%A8%E7%9A%84%E6%9B%B4%E5%A4%9A%E6%B5%8B%E8%AF%95/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

渗透技巧——"隐藏"注册表的更多测试

0x00 前言


在上篇文章《渗透技巧——”隐藏”注册表的创建》介绍了Poweliks使用过的注册表隐藏技术,分析原理,编写c程序实现功能

本文将做进一步测试,分享一种更为”隐蔽”的方法(该方法暂未找到公开资料,待定)

0x01 简介


本文将要介绍以下内容:

  • 使用Win32 API读取时的错误
  • “\0”放在字符串中间的情况
  • 其他Native API(如NtCreateFile)的应用
  • 更加隐蔽的利用方法

0x02 隐藏原理


对于Windows系统,”\0”(即0x0000)会被识别为字符串的结束符

所以在对该字符串读取的过程中,遇到开头的”\0”,会被解析成结束符,提前截断,导致读取错误

而使用Native API设定注册表,需要使用结构体OBJECT_ATTRIBUTES作为参数, 指定读取的字符串长度

只要长度设定正常,就能够读取正确的字符串,避免这个bug

利用的关键:

使用Native API多了一个参数,能够指定读取字符串的长度

那么,对该问题展开进一步思考,就有了如下测试

0x03 使用Win32 API读取时,具体是什么样的错误?


使用HiddenNtRegistry创建测试注册表键值,c++调用代码如下:

printf("=================Normal Key=================\n");
printf("1.CreateKey:\n");
MyCreateKey("\\Registry\\Machine\\Software\\test1");
printf("2.OpenKey:\n");
hKey = MyOpenKey("\\Registry\\Machine\\Software\\test1");
printf("3.SetValueKey:\n");
MySetValueKey(hKey,"test1","0123456789abcdef",REG_SZ);

printf("=================Hidden Key=================\n");
printf("1.OpenKey:\n");
hKey = MyOpenKey("\\Registry\\Machine\\Software\\test1");
printf("2.SetHiddenValueKey:\n");
MySetHiddenValueKey(hKey,"\0test1","hidden0123456789abcdef",REG_SZ);
printf("3.QueryHiddenValueKey:\n");
MyQueryHiddenValueKeyString(hKey,"\0test1");

程序实现以下功能:

  • 创建注册表键值test1,内容为0123456789abcdef
  • 创建注册表键值\0test1,内容为hidden0123456789abcdef

运行如下图

Alt text

使用Win32 API RegQueryValueEx尝试读取以上两个注册表键值

关键代码如下:

LONG lReturnCode = 0;
HKEY hkey;
LPCTSTR RegPath = _T("Software\\test1");
if (ERROR_SUCCESS == ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, RegPath, 0, KEY_READ, &hkey))
{
	char dwValue[1024];
	DWORD dwSzType = REG_SZ;
	DWORD dwSize = sizeof(dwValue);
	lReturnCode = ::RegQueryValueEx(hkey, _T("test1"), 0, &dwSzType, (LPBYTE)&dwValue, &dwSize);   
	if(lReturnCode != ERROR_SUCCESS)
	{
		printf("lReturnCode:%d\n",lReturnCode);
		if(lReturnCode = 2)
			printf("ERROR_FILE_NOT_FOUND\n");			
			return 0;
		}
		printf("RegQueryValue:");
		for (int i=0;i<dwSize/2-1;i++)
		{
			printf("%c",dwValue[i*2]);
		}
	}
	::RegCloseKey(hkey);

读取注册表键值test1,成功获取内容

读取注册表键值\0test1,修改代码如下:

lReturnCode = ::RegQueryValueEx(hkey, _T("\0test1"), 0, &dwSzType, (LPBYTE)&dwValue, &dwSize);

读取失败,返回ERROR_FILE_NOT_FOUND

验证上文原理: 由于”\0”的作用,字符串提前被截断,识别为空字符,导致无法获得名称

接着做进一步尝试

0x04 “\0”放在字符串中间会怎样?


HiddenNtRegistry的代码为:

printf("1.OpenKey:\n");
hKey = MyOpenKey("\\Registry\\Machine\\Software\\test2");
printf("2.SetHiddenValueKey:\n");
MySetHiddenValueKey2(hKey,"test2\0abc","hidden0123456789abcdef",REG_SZ);
printf("3.QueryHiddenValueKey:\n");
MyQueryHiddenValueKeyString2(hKey,"test2\0abc");

注:

原工程HiddenNtRegistry中的MySetHiddenValueKey函数和MyQueryHiddenValueKeyString函数需要作适当修改,重新计算字符串长度,新的函数命名为MySetHiddenValueKey2MyQueryHiddenValueKeyString2

程序实现以下功能:

  • 创建注册表键值test2\0abc,内容为hidden0123456789abcdef
  • 读取注册表键值test2\0abc的内容

运行如下图

Alt text

使用regedit.exe查询该键值,弹框提示无法获取,如下图

Alt text

这里可以做一个大胆的尝试:

既然test2\0abc中的”\0”会截断字符串,那么我们再创建一个名为test2的键值会怎么样呢?

创建注册表键值test2,内容为0123456789abcdef,关键代码如下:

hKey = MyOpenKey("\\Registry\\Machine\\Software\\test2");
MySetValueKey(hKey,"test2","0123456789abcdef",REG_SZ);

再次使用regedit.exe查看注册表,有趣的事情发生了,如下图

Alt text

查询注册表键值\Registry\Machine\Software\test2不再弹框报错,而是显示两个名为test2的键值,内容均为0123456789abcdef

我们知道,注册表不允许创建两个名称相同的注册表键值,而上述测试产生的两个同名键值,实际上是因为其中的一个被错误的截断,导致显示键值名称相同,键值内容也相同,为0123456789abcdef(实际上内容为hidden0123456789abcdef)

这样我们就又多了一种”隐藏”注册表的方法,相比于之前的在首位填”\0”,这个隐藏方法最大的优点是使用regedit.exe查看该键值时不会弹框报错,隐蔽效果更好,同时又具有欺骗性,同正常键值内容相同

对比如下图

Alt text

显示键值内容为0123456789abcdef,实际上为hidden0123456789abcdef

0x05 其他Native API(如NtCreateFile)能否应用?


参考NtCreateKey的实现思路,测试其他Native API,例如NtCreateFile,在创建文件时是否存在相同问题?

使用NtCreateFile创建特殊文件: \0c:\1\test.txt,关键代码如下:

HMODULE             hModule				= NULL;  
NTCREATEFILE        NtCreateFile		= NULL; 
UNICODE_STRING      FileName			= {0};  
OBJECT_ATTRIBUTES   ObjectAttributes	= {0};  
HANDLE              hFile1				= NULL;  
IO_STATUS_BLOCK     IOsb				= {0};  
HANDLE              hFile2				= INVALID_HANDLE_VALUE;  
PWCHAR              pBuffer				= NULL;  
DWORD               dwRet				= 0;  
hModule = LoadLibrary(_T("ntdll.dll"));  
if (!hModule)     
{  
	printf("Could not GetModuleHandle of NTDLL.DLL");
	return FALSE;
}   
NtCreateFile = (NTCREATEFILE)GetProcAddress(hModule, "NtCreateFile");  
if (!NtCreateFile) 
{
	printf("Could not find NtCreateFile entry point in NTDLL.DLL");
	return FALSE;
}	
char *Path = "\\Device\\\HarddiskVolume1\\1\\test.txt";
char *TempBuff;
TempBuff = (char*)malloc(strlen(Path+2)*2);
for(int i=0;i<strlen(Path);i++)
{
	TempBuff[(i+2)*2] = Path[i];
	TempBuff[(i+2)*2+1] = 0x00;
}
TempBuff[0] = 0x00;
TempBuff[1] = 0x00;
TempBuff[2] = 0x00;
TempBuff[3] = 0x00;
FileName.MaximumLength = MAX_PATH * sizeof(WCHAR);  
FileName.Length = (strlen(Path)+2)*sizeof(WCHAR);  
FileName.Buffer = (WCHAR *)TempBuff;
FileName.Buffer[FileName.Length] = L'\0';
InitializeObjectAttributes(&ObjectAttributes,&FileName,OBJ_CASE_INSENSITIVE,NULL,NULL);
NtStatus = NtCreateFile(&hFile1,  
							FILE_GENERIC_WRITE,  
							&ObjectAttributes,
							&IOsb,  
							NULL,  
							FILE_ATTRIBUTE_NORMAL,  
							0,
							FILE_SUPERSEDE,  
							FILE_SEQUENTIAL_ONLY,
							NULL,  
							0  
							); 
if (!NT_SUCCESS(NtStatus))  
{  
	printf("NtCreateFile failed (%x) \n", NtStatus);            
}  
else  
	printf("NtCreateFile succeed \n"); 

返回错误c000003b,表示STATUS_OBJECT_PATH_SYNTAX_BAD

调试程序,跟踪到InitializeObjectAttributes,查看结构体ObjectAttributes的参数,如下图

Alt text

查看Buffer在内存中的内容,如下图

Alt text

同NtCreateKey实现时的参数结构相同

对于NtCreateFile,暂时无法应用

0x06 利用思路与检测


Poweliks使用过的注册表隐藏技术,最大的问题是使用regedit.exe打开时会弹框报错,如果将\0插在字符串中间,同时新建一个\0前字符串的同名键值,就能避免这个问题

对此,检测思路就是要找到这种不寻常的注册表键值,查看注册表键值下是否存在两个相同名称的键值

如果利用这种方式在启动项位置新建注册表键值,使用Autoruns是能够检测出来的

0x07 小结


本文对Poweliks使用过的注册表隐藏技术做了进一步测试,分享一种更为隐蔽的利用方法,同时给出了防御检测的思路,对于其他Native API的应用,还需要更多测试


LEAVE A REPLY

Written on December 8, 2017

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK