欢迎光临
我们一直在努力

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

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

运行如下图

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

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

运行如下图

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

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

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

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

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

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


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

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

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

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

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

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

对比如下图

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

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

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

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

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

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

对于NtCreateFile,暂时无法应用

0x06 利用思路与检测


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

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

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

0x07 小结


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


LEAVE A REPLY

未经允许不得转载:杂术馆 » 渗透技巧——”隐藏”注册表的更多测试
分享到: 更多 (0)