发布于2022年11月8日3年前 0x00 前言 上一篇文章《渗透技巧——”隐藏”注册表的创建》介绍了Poweliks使用的注册表隐藏技术,分析了原理,并编写C程序实现了该功能。 本文将进一步测试并分享一种更“隐蔽”的方法(这种方法还没有找到公开信息,待定)。 0x01 简介 本文将介绍以下内容: 使用Win32 API读取时出错 当“\0”位于字符串中间时 其他原生API的应用(如NtCreateFile) 一种更微妙的利用方法 防御探测 0x02 隐藏原理 对于Windows系统,“\ 0”(0x 0000)被识别为字符串的终止符。 所以在读取这个字符串的过程中,开头的“\0”会被解释为终止符,会被提前截断,导致读取错误。 要使用Native API设置注册表,需要使用OBJECT_ATTRIBUTES结构作为参数来指定读取字符串的长度。 只要长度设置正常,就可以读取正确的字符串,避免这个bug。 利用的关键: 使用Native API还有一个额外的参数,可以指定读取字符串的长度。 那么,在进一步思考这个问题之后,就会有下面的测试。 0x03 使用Win32 API读取时,具体是什么样的错误? 使用HiddenNtRegistry创建测试注册表键值,C调用代码如下: printf('=================普通键==================\ n '); printf('1。create key:\ n '); MyCreateKey(' \ \ Registry \ \ Machine \ \ Software \ \ test1 '); printf('2。open key:\ n '); hKey=MyOpenKey(' \ \ Registry \ \ Machine \ \ Software \ \ test1 '); printf('3。SetValueKey:\ n '); MySetValueKey(hKey,' test1 ',' 0123456789abcdef ',REG _ SZ); printf('==================隐藏键=================\ n '); printf('1。open key:\ n '); hKey=MyOpenKey(' \ \ Registry \ \ Machine \ \ Software \ \ test1 '); printf('2。SetHiddenValueKey:\ n '); MySetHiddenValueKey(hKey,' \0test1 ',' hidden0123456789abcdef ',REG _ SZ); printf('3。QueryHiddenValueKey:\ n '); MyQueryHiddenValueKeyString(hKey,' \ 0 test 1 '); 该程序实现了以下功能: 创建内容为0123456789abcdef的注册表项test1。 创建内容为hidden0123456789abcdef的注册表项\0test1。 按如下方式运行 使用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 dw value[1024]; DWORD dwSzType=REG _ SZ DWORD dwSize=sizeof(dw value); lReturnCode=:regqueryvalueeex(HKEY,_T('test1 '),0,dwSzType,(LPBYTE)dwValue,dw size); if(lReturnCode!=错误_成功) { printf('lReturnCode:%d\n ',lReturnCode); if(lReturnCode=2) printf(' ERROR _ FILE _ NOT _ FOUND \ n '); 返回0; } printf(' regquery value:'); for(int I=0;我 读取注册表项test1,并成功获取内容。 读取注册表项\0test1,并按如下方式修改代码: lReturnCode=:regqueryvalueeex(HKEY,_T('\0test1 ')),0,dwSzType,(LPBYTE)dwValue,dw size); 读取失败,返回ERROR_FILE_NOT_FOUND。 验证了上面的原理:由于“\0”的作用,字符串被提前截断,识别为空字符,因此无法获得名称。 然后再做进一步的尝试。 0x04 “\0”放在字符串中间会怎样? HiddenNtRegistry的代码是: printf('1。open key:\ n '); hKey=MyOpenKey(' \ \ Registry \ \ Machine \ \ Software \ \ test 2 '); printf('2。SetHiddenValueKey:\ n '); mysethiddenvaluey 2(hKey,' test2\0abc ',' hidden0123456789abcdef ',REG _ SZ); printf('3。QueryHiddenValueKey:\ n '); myqueryhiddenvaluekeystring 2(hKey,' test 2 \ 0 ABC '); 注: 需要适当修改原项目HiddenNtRegistry中的MySetHiddenValueKey函数和MyQueryHiddenValueKeyString函数来重新计算字符串长度。新函数被命名为MySetHiddenValueKey2string2和MyQueryHiddenValueKeyString2。 该程序实现了以下功能: 创建注册表项test2\0abc,内容为hidden0123456789abcdef。 阅读注册表项test2\0abc的内容 按如下方式运行 使用regedit.exe查询键值,得不到弹出提示,如下图所示。 这里有一个大胆的尝试: 既然test2\0abc中的”\0”会截断字符串,那么我们再创建一个名为test2的键值会怎么样呢? 创建注册表项test2,内容为0123456789abcdef,键码如下: hKey=MyOpenKey(' \ \ Registry \ \ Machine \ \ Software \ \ test 2 '); 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文件名={ 0 }; OBJECT_ATTRIBUTES对象属性={ 0 }; HANDLE hFile1=NULL IO _ STATUS _ BLOCK IOsb={ 0 }; HANDLE hfile 2=INVALID _ HANDLE _ VALUE; PWCHAR pBuffer=NULL DWORD dwRet=0; h module=LoadLibrary(_ T(' ntdll . dll ')); 如果(!hModule) { printf('无法获取NTDLL的ModuleHandle。DLL’); 返回FALSE } NtCreateFile=(NtCreateFile)GetProcAddress(h module,' NtCreateFile '); 如果(!NtCreateFile) { printf('在NTDLL中找不到NtCreateFile入口点。DLL’); 返回FALSE } char * Path=' \ \ Device \ \ \ hard disk volume 1 \ \ 1 \ \ test . txt '; char * TempBuff TempBuff=(char *)malloc(strlen(Path 2)* 2); for(int I=0;我 返回错误c000003b,指示STATUS_OBJECT_PATH_SYNTAX_BAD。 调试器,跟踪到InitializeObjectAttributes,并检查结构ObjectAttributes的参数,如下图所示 检查内存中缓冲区的内容,如下图所示 与实现NtCreateKey时的参数结构相同。 对于NtCreateFile,暂时不能应用。 0x06 利用思路与检测 Poweliks使用的注册表隐藏技术最大的问题是,使用regedit.exe打开时,会弹出一个框,报错。如果在字符串中间插入\0,并在\0之前创建一个同名的新键值,就可以避免这个问题。 对此,检测思路是找到这个不寻常的注册表项,检查注册表项下是否有两个同名的项。 如果您以这种方式在启动键位置创建了一个新的注册表键值,则可以使用Autoruns检测到它。 0x07 小结 在本文中,我们进一步测试了Poweliks使用的注册表隐藏技术,分享了一个更加隐蔽的利用方法,并给出了防御检测的思路。对于其他原生API应用,需要更多的测试。 留下回复
创建帐户或登录后发表意见