回归最本质的信息安全

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

2017年12月22日发布

40,079
0
0

导语:在上篇文章《渗透技巧——”隐藏”注册表的创建》介绍了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

运行如下图

01.png

使用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的内容

运行如下图

02.png

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

03.png

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

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

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

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

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

05.png

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

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

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

对比如下图

04.png

显示键值内容为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的参数,如下图

06.png

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

07.png

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

对于NtCreateFile,暂时无法应用

0x06 利用思路与检测

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

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

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

0x07 小结

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

本文为 3gstudent 原创稿件, 授权嘶吼独家发布,如若转载,请联系嘶吼编辑: http://www.4hou.com/penetration/9132.html

点赞 0
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

3gstudent

嘶吼特约作者

发私信

发表评论