uaf与句柄

x64dbg 官博翻译

x64dbg的官博质量很高,这篇文章和句柄相关,翻译mark

原文链接 https://x64dbg.com/blog/

昨天我在调试一些程序,在重新启动调试器后,我看见status标签一直卡在初始化。之后便崩溃

重现步骤:
- 加载一些调试器
- 保持单步一段时间
- 按下重新开始
- 重复直到bug出现

现象:
- 标签卡在初始化
- 标签卡在暂停

发现端倪

在获得稳定的重现后,我开始寻找这为什么发生。TaskThread看起来是正确的,但由于WakeUp函数可能失败,我在ReleaseSemaphore上放置了一个断言,这应该能出发TaskThreadl:

template <typename F, typename... Args>
void TaskThread_<F, Args...>::WakeUp(Args... _args)
{
    ++this->wakeups;
    EnterCriticalSection(&this->access);
    this->args = CompressArguments(std::forward<Args>(_args)...);
    LeaveCriticalSection(&this->access);
    // This will fail silently if it's redundant, which is what we want.
    if(!ReleaseSemaphore(this->wakeupSemaphore, 1, nullptr))
        __debugbreak();
}

我尝试重现bug,不出意外断言命中了。这时,我猜测是内存崩溃,所以我在TaskThread插入了一些调试手段,来在一块安全的内存区域中储存之前的句柄

struct DebugStruct
{
    HANDLE wakeupSemaphore = nullptr;
};

template <int N, typename F, typename... Args>
TaskThread_<N, F, Args...>::TaskThread_(F fn,
                                     size_t minSleepTimeMs, DebugStruct* debug) : fn(fn), minSleepTimeMs(minSleepTimeMs)
{
    //make the semaphore named to find it more easily in a handles viewer
    wchar_t name[256];
    swprintf_s(name, L"_TaskThread%d_%p", N, debug);
    this->wakeupSemaphore = CreateSemaphoreW(nullptr, 0, 1, name);
    if(debug)
    {
        if(!this->wakeupSemaphore)
            __debugbreak();
        debug->wakeupSemaphore = this->wakeupSemaphore;
    }
    InitializeCriticalSection(&this->access);

    this->thread = std::thread([this, debug]
    {
        this->Loop(debug);
    });
}

TaskThread 实例现在被初始化然后像这样调用


void GuiSetDebugStateAsync(DBGSTATE state)
{
    GuiSetDebugStateFast(state);
    static TaskThread_<
               6,
               decltype(&GuiSetDebugState),
               DBGSTATE>
           GuiSetDebugStateTask(
               &GuiSetDebugState,
               300,
               new (VirtualAlloc(0,
                                 sizeof(DebugStruct),
                                 MEM_RESERVE | MEM_COMMIT,
                                 PAGE_EXECUTE_READWRITE)
                   ) DebugStruct()
           );
    GuiSetDebugStateTask.WakeUp(state, true);
}

现在我启动x64dbg并且使用Process Hacker找到_TaskThread6_XXXXXXXX 信号量,记下句柄。然后我重现并且惊讶的发现wakeupSemaphore是0x640,和启动的一样。

但是,当我再次检查检查句柄视图,0x640不再是信号量的句柄,而是一个映射文件

碰一碰运气

感觉显示WinAPI的错误使用,Application Verifier可以检查这类错误,但是我不会用,所以只能自己撸了。

方法很简单
1. 挂钩CloseHandle API
1. 吧当前的信号量句柄保存到一个全局变量中
1. 如果过句柄已经被释放就崩溃

static DebugStruct* g_Debug = nullptr;
typedef BOOL(WINAPI* CLOSEHANDLE)(HANDLE hObject);
static CLOSEHANDLE fpCloseHandle = nullptr;

static BOOL WINAPI CloseHandleHook(HANDLE hObject)
{
    if(g_Debug && g_Debug->wakeupSemaphore == hObject)
        __debugbreak();
    return fpCloseHandle(hObject);
}

static void DoHook()
{
    if(MH_Initialize() != MH_OK)
        __debugbreak();
    if(MH_CreateHook(GetProcAddress(GetModuleHandleW(L"kernelbase.dll"), "CloseHandle"), &CloseHandleHook, (LPVOID*)&fpCloseHandle) != MH_OK)
        __debugbreak();
    if(MH_EnableHook(MH_ALL_HOOKS) != MH_OK)
        __debugbreak();
}

成功吃鸡

bug发生在TitanEngine模块,ForceClose方法被期望于关闭所有Dll句柄

但是为什么信号量句柄回合之前的一个文件句柄相同呢,答案就在以下事件

  1. LOAD_DLL_DEBUG_EVENT 从链表中获取文件句柄
  2. LOAD_DLL_DEBUG_EVENT 立即关闭之前的文件句柄在调试会话
  3. 当调试器第一次中断并且信号量被用之前的文件句柄(现在被关闭了)创建,TaskThread的静态初始化器被调用
  4. 这一切都运转正常,知道ForceClose方法被调用并且来自于LOAD_DLL_DEBUG_EVENT文件句柄再次被关闭
  5. TaskThread中断
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值