Loading... # 采用第三方库BlackBone实现主线程调用call [Blackbone](https://github.com/DarthTon/Blackbone)是windows操作系统下一款内存hack库,具体功能介绍移步其github页:https://github.com/DarthTon/Blackbone。 ## 一、BlackBone编译 使用Visual Studio 2019完成编译,特别注意,C++标准需使用17版本。 编译到静态库。 ## 二、使用 静态库包含`BlackBone.lib`。 在`LuaFunc.cpp`文件头部添加`#include "..\BlackBone\BlackBone\Process\Process.h"`并添加如下代码: ```c++ uintptr_t g_gameBase = 0; //游戏主模块基址 blackbone::Process g_process; bool LuaFuncInit() { g_process.Attach(GetCurrentProcessId()); //g_process 初始化 g_gameBase = g_process.modules().GetMainModule().get()->baseAddress;//游戏主模块基址初始化 return true; } ``` 参考BlackBone源码中Sample项目示例: ```c++ auto& remote = notepad.remote(); auto GetModuleHandleWPtr = notepad.modules().GetExport( L"kernel32.dll", "GetModuleHandleW" ); auto mainThread = notepad.threads().getMain(); //获取主线程ID //AsmFactory::GetAssembler()为AsmJit进行的封装 //AsmJit是一个完整的JIT(just In Time, 运行时刻)的针对C++语言的汇编器, //可以生成兼容x86和x64架构的原生代码,不仅支持整个x86/x64的指令集(包括传统的MMX和最新的AVX2指令集) //,而且提供了一套可以在编译时刻进行语义检查的API。 if (auto asmPtr = AsmFactory::GetAssembler(); asmPtr && mainThread && GetModuleHandleWPtr) { auto& a = *asmPtr; a.GenPrologue(); /// <summary> /// 原生函数调用 /// </summary> /// <param name="pFN">函数指针</param> /// <param name="args">函数参数列表</param> /// <param name="cc">函数调用约定</param> a.GenCall( static_cast<uintptr_t>(GetModuleHandleWPtr->procAddress), { nullptr }, cc_stdcall ); a.GenEpilogue(); uint64_t result = 0; /// <summary> /// 在任意存在的线程中执行代码 /// </summary> /// <param name="pCode">需要执行的代码</param> /// <param name="size">代码字节长度</param> /// <param name="callResult">返回值</param> /// <param name="thd">目标线程</param> /// <returns>Status</returns> remote.ExecInAnyThread( a->make(), a->getCodeSize(), result, mainThread ); } ``` 根据上述示例及连接服务器函数原型,我们可以轻易写出如下代码: ```c/c++ int ConnectToRealm(lua_State* L){ int n = lua_gettop(L); if (n < 1) return 0; unsigned int realmID = lua_tonumber(L, 1); //服务器ID if (g_gameBase + offset::LoginState_6) { if (utils::read<BYTE>(g_gameBase + offset::LoginState_6 + offset::LoginState_6_Offset)) { //根据ID获取ptr auto v2 = sub_141B6C8D0(g_gameBase + offset::LoginState_6, realmID); if (v2) { //主线程调用call //sub_141ACA530(v2 + 0x138, realmID, v2); if (auto asmPtr = blackbone::AsmFactory::GetAssembler(); asmPtr ) { auto& a = *asmPtr; a.GenPrologue(); a.GenCall(g_gameBase + offset::Login_ConnectToServer, { v2 + 0x138, realmID, v2 }, blackbone::cc_stdcall); a.GenEpilogue(); uint64_t result = 0; auto mainThread = g_process.threads().getMain(); g_process.remote().ExecInAnyThread(a->make(), a->getCodeSize(), result, mainThread); lua_pushboolean(L, 1); return 1; } } } } return 0; } ``` ## 三、深入聊聊 扒拉了一下`ExecInAnyThread`的源码,如下文所示: ```c++ /// <summary> /// Execute code in context of any existing thread /// </summary> /// <param name="pCode">Cde to execute</param> /// <param name="size">Code size.</param> /// <param name="callResult">Execution result</param> /// <param name="thd">Target thread</param> /// <returns>Status</returns> NTSTATUS RemoteExec::ExecInAnyThread( PVOID pCode, size_t size, uint64_t& callResult, ThreadPtr& thd ) { NTSTATUS status = STATUS_SUCCESS; _CONTEXT32 ctx32 = { 0 }; _CONTEXT64 ctx64 = { 0 }; assert( _hWaitEvent != NULL ); if (_hWaitEvent == NULL) return STATUS_NOT_FOUND; // 将需要执行的pCode拷贝到目标区域 if (!NT_SUCCESS( status = CopyCode( pCode, size ) )) return status; if (_hWaitEvent) ResetEvent( _hWaitEvent ); //暂停线程 if (!thd->Suspend()) return LastNtStatus(); //call 调用外层构造 auto a = AsmFactory::GetAssembler( _process.core().isWow64() ); if (!_process.core().isWow64()) //判断目标进程指令架构 { //x64 const int count = 15; static const asmjit::GpReg regs[] = { asmjit::host::rax, asmjit::host::rbx, asmjit::host::rcx, asmjit::host::rdx, asmjit::host::rsi, asmjit::host::rdi, asmjit::host::r8, asmjit::host::r9, asmjit::host::r10, asmjit::host::r11, asmjit::host::r12, asmjit::host::r13, asmjit::host::r14, asmjit::host::r15, asmjit::host::rbp }; if (!NT_SUCCESS( status = thd->GetContext( ctx64, CONTEXT64_CONTROL, true ) )) { thd->Resume(); return status; } // // Preserve thread context // I don't care about FPU, XMM and anything else // Stack must be aligned on 16 bytes // (*a)->sub( asmjit::host::rsp, count * sizeof( uint64_t ) ); (*a)->pushf(); // Save registers for (int i = 0; i < count; i++) (*a)->mov( asmjit::Mem( asmjit::host::rsp, i * sizeof( uint64_t ) ), regs[i] ); a->GenCall( _userCode[_currentBufferIdx].ptr(), { _userData[_currentBufferIdx].ptr() } ); AddReturnWithEvent( *a, mt_mod64, rt_int32, INTRET_OFFSET ); // Restore registers for (int i = 0; i < count; i++) (*a)->mov( regs[i], asmjit::Mem( asmjit::host::rsp, i * sizeof( uint64_t ) ) ); (*a)->popf(); (*a)->add( asmjit::host::rsp, count * sizeof( uint64_t ) ); // jmp [rip] (*a)->dw( '\xFF\x25' ); (*a)->dd( 0 ); (*a)->dq( ctx64.Rip ); } else { //x86 if (!NT_SUCCESS( status = thd->GetContext( ctx32, CONTEXT_CONTROL, true ) )) { thd->Resume(); return status; } (*a)->pusha(); (*a)->pushf(); a->GenCall( _userCode[_currentBufferIdx].ptr(), { _userData[_currentBufferIdx].ptr() } ); (*a)->add( asmjit::host::esp, sizeof( uint32_t ) ); AddReturnWithEvent( *a, mt_mod32, rt_int32, INTRET_OFFSET ); //event返回 (*a)->popf(); (*a)->popa(); (*a)->push( static_cast<int>(ctx32.Eip) ); (*a)->ret(); } //设置目标线程EIP/RIP 为我们自己代码起始地址 if (NT_SUCCESS( status = _userCode[_currentBufferIdx].Write( size, (*a)->getCodeSize(), (*a)->make() ) )) { if (_process.core().isWow64()) { ctx32.Eip = static_cast<uint32_t>(_userCode[_currentBufferIdx].ptr() + size); status = thd->SetContext( ctx32, true ); } else { ctx64.Rip = _userCode[_currentBufferIdx].ptr() + size; status = thd->SetContext( ctx64, true ); } } //恢复线程运行 thd->Resume(); if (NT_SUCCESS( status )) { //等待Event事件 WaitForSingleObject( _hWaitEvent, 20 * 1000/*INFINITE*/ ); status = _userData[_currentBufferIdx].Read( INTRET_OFFSET, callResult ); } SwitchActiveBuffer(); return status; } ``` 发现,主要做了如下几件事情: 1. 暂停线程; 2. 调用call的shellcode构造及写入; 3. 保存目标线程context环境 4. 修改目标线程EIP为shellcode入口; 5. 构造的shellcode执行完成后恢复目标线程context环境; 6. 等待event返回; 7. 返回结果。 鉴于此,我们可以愉快的偷偷摸摸的做很多事情啦~ ## 四、注意事项 若需要调用的目标call进行了浮点运算(FPU/XMM相关寄存器的值可能发生变化),这将导致call调用前后线程context不一致,需要进行处理。 最后修改:2021 年 09 月 22 日 11 : 36 AM © 禁止转载