设为首页收藏本站
查看: 8231|回复: 2

[原创] [翻译]反调试:对象句柄

[复制链接]
  • TA的每日心情
    开心
    2022-1-9 18:41
  • 签到天数: 5 天

    [LV.2]偶尔看看I

    发表于 2021-9-12 15:29:40 | 显示全部楼层 |阅读模式
    备注
    原文地址:https://anti-debug.checkpoint.com/techniques/object-handles.html
    原文标题:Anti-Debug: Object Handles
    更新日期:2021年6月9日
    此文后期:根据自身所学进行内容扩充
    因自身技术有限,只能尽自身所能翻译国外技术文章,供大家学习,若有不当或可完善的地方,希望可以指出,用于共同完善这篇文章。



    目录
    • 对象句柄
    • 1. OpenProcess()
    • 2. CreateFile()
    • 3. CloseHandle()
    • 4. LoadLibrary()
    • 5. NtQueryObject()
    • 反制措施

    对象句柄
    下面的一组技术代表了使用内核对象句柄来检测调试器存在的检查。一些接受内核对象句柄作为参数的WinAPI函数在调试时可能会有不同的行为,或者会因为调试器的实现而产生副作用。此外,在调试开始时,操作系统会创建特定的内核对象。
    1. OpenProcess()
    一些调试器可以通过在csrss.exe进程上使用kernel32!OpenProcess()函数来检测。只有当该进程的用户是管理员组的成员并且有调试权限时,该调用才会成功。
    C/C++ 代码:
    1. typedef DWORD (WINAPI *TCsrGetProcessId)(VOID);

    2. bool Check()
    3. {   
    4.     HMODULE hNtdll = LoadLibraryA("ntdll.dll");
    5.     if (!hNtdll)
    6.         return false;
    7.    
    8.     TCsrGetProcessId pfnCsrGetProcessId = (TCsrGetProcessId)GetProcAddress(hNtdll, "CsrGetProcessId");
    9.     if (!pfnCsrGetProcessId)
    10.         return false;

    11.     HANDLE hCsr = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pfnCsrGetProcessId());
    12.     if (hCsr != NULL)
    13.     {
    14.         CloseHandle(hCsr);
    15.         return true;
    16.     }        
    17.     else
    18.         return false;
    19. }
    复制代码


    2. CreateFile()
    当CREATE_PROCESS_DEBUG_EVENT事件发生时,被调试文件的句柄被保存在CREATE_PROCESS_DEBUG_INFO结构中。因此,调试器可以从这个文件中读取调试信息。如果这个句柄没有被调试器关闭,这个文件就不会被打开独占访问。有些调试器会忘记关闭这个句柄。

    这个技巧使用kernel32!CreateFileW()(或者kernel32!CreateFileA())来独占打开当前进程的文件。如果调用失败,我们可以认为当前进程是在有调试器的情况下运行的。
    C/C++ 代码:
    1. bool Check()
    2. {
    3.     CHAR szFileName[MAX_PATH];
    4.     if (0 == GetModuleFileNameA(NULL, szFileName, sizeof(szFileName)))
    5.         return false;
    6.    
    7.     return INVALID_HANDLE_VALUE == CreateFileA(szFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);
    8. }
    复制代码


    3. CloseHandle()
    如果一个进程在调试器下运行,并且一个无效的句柄被传递给ntdll!NtClose()或kernel32!CloseHandle()函数,那么将引发EXCEPTION_INVALID_HANDLE(0xC0000008)异常。这个异常可以被一个异常处理程序缓存起来。如果控制被传递给异常处理程序,表明有一个调试器存在。
    C/C++ 代码:
    1. bool Check()
    2. {
    3.     __try
    4.     {
    5.         CloseHandle((HANDLE)0xDEADBEEF);
    6.         return false;
    7.     }
    8.     __except (EXCEPTION_INVALID_HANDLE == GetExceptionCode()
    9.                 ? EXCEPTION_EXECUTE_HANDLER
    10.                 : EXCEPTION_CONTINUE_SEARCH)
    11.     {
    12.         return true;
    13.     }
    14. }
    复制代码


    4. LoadLibrary()
    当使用kernel32!LoadLibraryW()(或kernel32!LoadLibraryA())函数将一个文件加载到进程内存时,LOAD_DLL_DEBUG_EVENT事件发生。被加载文件的句柄将被保存在LOAD_DLL_DEBUG_INFO结构中。因此,调试器可以从这个文件中读取调试信息。如果这个句柄没有被调试器关闭,这个文件就不会被打开独占访问。有些调试器会忘记关闭这个句柄。

    为了检查调试器的存在,我们可以用kernel32!LoadLibraryA()加载任何文件,并尝试用kernel32!CreateFileA()独家打开它。如果kernel32!CreateFileA()调用失败,说明调试器是存在的。
    C/C++ 代码:
    1. bool Check()
    2. {
    3.     CHAR szBuffer[] = { "C:\\Windows\\System32\\calc.exe" };
    4.     LoadLibraryA(szBuffer);
    5.     return INVALID_HANDLE_VALUE == CreateFileA(szBuffer, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
    6. }
    复制代码


    5. NtQueryObject()
    当一个调试会话开始时,一个叫做 “debug object”的内核对象被创建,并有一个句柄与之关联。使用ntdll!NtQueryObject()函数,可以查询现有对象的列表,并检查与任何存在的调试对象相关的句柄数量。

    然而这个技术不能确定当前进程是否正在被调试。它只能显示自系统启动以来,调试器是否在系统上运行。
    C/C++ 代码:
    1. typedef struct _OBJECT_TYPE_INFORMATION
    2. {
    3.     UNICODE_STRING TypeName;
    4.     ULONG TotalNumberOfHandles;
    5.     ULONG TotalNumberOfObjects;
    6. } OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;

    7. typedef struct _OBJECT_ALL_INFORMATION
    8. {
    9.     ULONG NumberOfObjects;
    10.     OBJECT_TYPE_INFORMATION ObjectTypeInformation[1];
    11. } OBJECT_ALL_INFORMATION, *POBJECT_ALL_INFORMATION;

    12. typedef NTSTATUS (WINAPI *TNtQueryObject)(
    13.     HANDLE                   Handle,
    14.     OBJECT_INFORMATION_CLASS ObjectInformationClass,
    15.     PVOID                    ObjectInformation,
    16.     ULONG                    ObjectInformationLength,
    17.     PULONG                   ReturnLength
    18. );

    19. enum { ObjectAllTypesInformation = 3 };

    20. #define STATUS_INFO_LENGTH_MISMATCH 0xC0000004

    21. bool Check()
    22. {
    23.     bool bDebugged = false;
    24.     NTSTATUS status;
    25.     LPVOID pMem = nullptr;
    26.     ULONG dwMemSize;
    27.     POBJECT_ALL_INFORMATION pObjectAllInfo;
    28.     PBYTE pObjInfoLocation;
    29.     HMODULE hNtdll;
    30.     TNtQueryObject pfnNtQueryObject;
    31.    
    32.     hNtdll = LoadLibraryA("ntdll.dll");
    33.     if (!hNtdll)
    34.         return false;
    35.         
    36.     pfnNtQueryObject = (TNtQueryObject)GetProcAddress(hNtdll, "NtQueryObject");
    37.     if (!pfnNtQueryObject)
    38.         return false;

    39.     status = pfnNtQueryObject(
    40.         NULL,
    41.         (OBJECT_INFORMATION_CLASS)ObjectAllTypesInformation,
    42.         &dwMemSize, sizeof(dwMemSize), &dwMemSize);
    43.     if (STATUS_INFO_LENGTH_MISMATCH != status)
    44.         goto NtQueryObject_Cleanup;

    45.     pMem = VirtualAlloc(NULL, dwMemSize, MEM_COMMIT, PAGE_READWRITE);
    46.     if (!pMem)
    47.         goto NtQueryObject_Cleanup;

    48.     status = pfnNtQueryObject(
    49.         (HANDLE)-1,
    50.         (OBJECT_INFORMATION_CLASS)ObjectAllTypesInformation,
    51.         pMem, dwMemSize, &dwMemSize);
    52.     if (!SUCCEEDED(status))
    53.         goto NtQueryObject_Cleanup;

    54.     pObjectAllInfo = (POBJECT_ALL_INFORMATION)pMem;
    55.     pObjInfoLocation = (PBYTE)pObjectAllInfo->ObjectTypeInformation;
    56.     for(UINT i = 0; i < pObjectAllInfo->NumberOfObjects; i++)
    57.     {

    58.         POBJECT_TYPE_INFORMATION pObjectTypeInfo =
    59.             (POBJECT_TYPE_INFORMATION)pObjInfoLocation;

    60.         if (wcscmp(L"DebugObject", pObjectTypeInfo->TypeName.Buffer) == 0)
    61.         {
    62.             if (pObjectTypeInfo->TotalNumberOfObjects > 0)
    63.                 bDebugged = true;
    64.             break;
    65.         }

    66.         // Get the address of the current entries
    67.         // string so we can find the end
    68.         pObjInfoLocation = (PBYTE)pObjectTypeInfo->TypeName.Buffer;

    69.         // Add the size
    70.         pObjInfoLocation += pObjectTypeInfo->TypeName.Length;

    71.         // Skip the trailing null and alignment bytes
    72.         ULONG tmp = ((ULONG)pObjInfoLocation) & -4;

    73.         // Not pretty but it works
    74.         pObjInfoLocation = ((PBYTE)tmp) + sizeof(DWORD);
    75.     }

    76. NtQueryObject_Cleanup:
    77.     if (pMem)
    78.         VirtualFree(pMem, 0, MEM_RELEASE);

    79.     return bDebugged;
    80. }
    复制代码


    反制措施
    反制这些检查的最简单的方法是只需手动跟踪程序直到检查,然后跳过它(例如用NOP补丁或改变指令指针或在检查后改变零标志寄存器)。

    如果你写一个反调试方案,你需要拦截列出的函数,在分析其输入后改变返回值:
    • ntdll!OpenProcess:如果第三个参数是csrss.exe的句柄,则返回NULL。
    • ntdll!NtClose:你可以使用ntdll!NtQueryObject()检查是否可以检索到关于输入句柄的任何信息,如果句柄无效,则不显示异常。
    • ntdll!NtQueryObject: 如果ObjectAllTypesInformation类被查询,从结果中过滤调试对象。

    以下技术应该在没有拦截的情况下处理:
    • ntdll!NtCreateFile: 太通用了,无法反制。然而,如果你为一个特定的调试器写一个插件,你可以确保被调试文件的句柄被关闭。
    • kernel32!LoadLibraryW/A:没有反制措施。

    点评

    支持一下  发表于 2024-10-14 19:29
  • TA的每日心情
    开心
    6 天前
  • 签到天数: 575 天

    [LV.9]以坛为家II

    发表于 2024-10-14 19:30:58 | 显示全部楼层
    谢谢分享,已回复。
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    红盟社区--红客联盟 

    Processed in 0.061973 second(s), 22 queries.

    站点统计| 举报| Archiver| 手机版| 黑屋 |   

    备案号:冀ICP备20006029号-1 Powered by HUC © 2001-2021 Comsenz Inc.

    手机扫我进入移动触屏客户端

    关注我们可获取更多热点资讯

    Honor accompaniments. theme macfee

    快速回复 返回顶部 返回列表