导语:在上篇文章《渗透技巧——”隐藏”注册表的创建》介绍了Poweliks使用过的注册表隐藏技术,分析原理,编写c程序实现功能 本文将做进一步测试,分享一种更为”隐蔽”的方法
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
运行如下图
使用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函数需要作适当修改,重新计算字符串长度,新的函数命名为MySetHiddenValueKey2和MyQueryHiddenValueKeyString2
程序实现以下功能:
·创建注册表键值test2\0abc,内容为hidden0123456789abcdef
·读取注册表键值test2\0abc的内容
运行如下图
使用regedit.exe查询该键值,弹框提示无法获取,如下图
这里可以做一个大胆的尝试:
既然test2\0abc中的”\0”会截断字符串,那么我们再创建一个名为test2的键值会怎么样呢?
创建注册表键值test2,内容为0123456789abcdef,关键代码如下:
hKey = MyOpenKey("\\Registry\\Machine\\Software\\test2"); MySetValueKey(hKey,"test2","0123456789abcdef",REG_SZ);
再次使用regedit.exe查看注册表,有趣的事情发生了,如下图
查询注册表键值\Registry\Machine\Software\test2不再弹框报错,而是显示两个名为test2的键值,内容均为0123456789abcdef
我们知道,注册表不允许创建两个名称相同的注册表键值,而上述测试产生的两个同名键值,实际上是因为其中的一个被错误的截断,导致显示键值名称相同,键值内容也相同,为0123456789abcdef(实际上内容为hidden0123456789abcdef)
这样我们就又多了一种”隐藏”注册表的方法,相比于之前的在首位填”\0”,这个隐藏方法最大的优点是使用regedit.exe查看该键值时不会弹框报错,隐蔽效果更好,同时又具有欺骗性,同正常键值内容相同
对比如下图
显示键值内容为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的参数,如下图
查看Buffer在内存中的内容,如下图
同NtCreateKey实现时的参数结构相同
对于NtCreateFile,暂时无法应用
0x06 利用思路与检测
Poweliks使用过的注册表隐藏技术,最大的问题是使用regedit.exe打开时会弹框报错,如果将\0插在字符串中间,同时新建一个\0前字符串的同名键值,就能避免这个问题
对此,检测思路就是要找到这种不寻常的注册表键值,查看注册表键值下是否存在两个相同名称的键值
如果利用这种方式在启动项位置新建注册表键值,使用Autoruns是能够检测出来的
0x07 小结
本文对Poweliks使用过的注册表隐藏技术做了进一步测试,分享一种更为隐蔽的利用方法,同时给出了防御检测的思路,对于其他Native API的应用,还需要更多测试