0x0 前言 目前已经定位到动态库了,现在就来看看里面的功能逻辑是咋写的,这个环节的话其实跟reverse感觉关系已经不大了,更多的是需要对UE4引擎的理解。今天花了点时间,结合dump的游戏SDK、libUE4,把这个文件里的一些函数和变量名给还原了出来。
0x1 找到入口函数 1.首先是要定位到它的入口函数,一般来说动态库会用__attribute__((constructor)) void _init()指定,这个函数会在dlopen返回前调用。IDA里ctrl+s看segment表,找.init_array,定位到了第二个函数就是外挂逻辑的起始函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 int __cdecl hackmain() { pthread_t v1[2]; // [xsp+0h] [xbp-20h] BYREF v1[1] = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40); pthread_create(v1, 0LL, (void *(*)(void *))mainthread_42FA0, 0LL); return __android_log_print(3LL, &byte_E81A4, (const char *)&xmmword_E81B0); }
进那个mainthread_42FA0里看看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 __int64 mainthread_42FA0() { v0 = Ram_uintptr_t_3CE10(&qword_E8198); v1 = qword_E9940; libUE4_E91D0 = v0; if ( !qword_E9940 ) { for ( i = *(_QWORD *)(v0 + 0xXXXXXXX); ; i = *(_QWORD *)(libUE4_E91D0 + 0xXXXXXX) ) { qword_E9940 = i; sleep(1u); v1 = qword_E9940; if ( qword_E9940 ) break; } } if ( *(__int64 (__fastcall **)(__int64, __int64))(v1 + 16) != sub_42F30 ) { qword_E9A48 = *(__int64 (__fastcall **)(_QWORD, _QWORD))(v1 + 16); *(_QWORD *)(v1 + 16) = sub_42F30; } inlinehook(libUE4_E91D0 + 0xXXXXXX, &ESP_42E44, &qword_E9A40); inlinehook(libUE4_E91D0 + 0xXXXXXX, bulletTrack_3F4D8, &qword_E9A20); return 0LL; }
这个ESP_42E44,和bulletTrack_3F4D8就是功能相关的代码。
0x2 绘制逻辑 首先看看绘制的实现,他这个绘制是引擎层的,hook的UGameViewportClient::PostRender(this*,UCanvas*)这个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 // UGameViewportClient::PostRender __int64 __fastcall ESP_42E44(__int64 gameViewportclient, __int64 Ucanvas) { if ( !myUcanvas_E9288 ) myUcanvas_E9288 = Ucanvas; windowWidth_E91E4 = ANativeWindow_getWidth(); windowHeight_E91E0 = ANativeWindow_getHeight(); screenSizeX_E91DC = RAM_int_3F82C(Ucanvas + 0x40); v4 = RAM_int_3F82C(Ucanvas + 0x44); K2DrawTextFuncPtr_E92A8 = (__int64 (__fastcall *)(_QWORD, __int64, float, float, float, float, float, float, float, float, float))(libUE4_E91D0 + K2DrawLineOffset_E802C); K2DrawLineFuncPtr_E92B0 = (__int64 (__fastcall *)(_QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, long double, float, long double, long double, long double, long double, long double, long double, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD))(libUE4_E91D0 + K2DrawTextOffset_E8030); screenSizeY_E91D8 = v4; unknownFunc_E9210 = (__n128 (__fastcall *)(_QWORD, _QWORD, _QWORD))(libUE4_E91D0 + unknownFuncOffset1_E804C); initData_3F710(); // engineFontInit_3F5BC(); myESPLogic_3F934(screenSizeX_E91DC, screenSizeY_E91D8);// 外挂的绘制逻辑 return qword_E9A40(gameViewportclient, Ucanvas); }
还原它initData和engineFontInit里初始化数据的变量名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 __int64 initData_3F710() { __int64 v0; // x0 __int64 WeaponCoreComponent; // x0 __int64 result; // x0 Uworld_E9960 = RAM_uintptr_t_3D610(libUE4_E91D0 + Uworld_E8038); Ulevel_E9968 = RAM_uintptr_t_3D610(Uworld_E9960 + 0x30); Array_E9970 = RAM_uintptr_t_3D610(Ulevel_E9968 + 0x98); arrayCount_E9978 = RAM_int_3F82C(Ulevel_E9968 + 0xA0); v0 = RAM_uintptr_t_3D610(libUE4_E91D0 + LocalPlayer_E8044); selfPawn_E9208 = RAM_uintptr_t_3D610(v0 + 0x30); PlayerCameraManager_E9200 = RAM_uintptr_t_3D610(selfPawn_E9208 + PlayerCameraManagerOffset_E8010); selfPawn_E9980 = RAM_uintptr_t_3D610(selfPawn_E9208 + ControllerWeaponViewPawnOffset_E8014); WeaponCoreComponent = RAM_uintptr_t_3D610(selfPawn_E9980 + 0xXXXX); WeaponDataAsset_E9988 = RAM_uintptr_t_3D610(WeaponCoreComponent + 0xXXX); localPlayer_E9958 = RAM_uintptr_t_3D610(libUE4_E91D0 + LocalPlayer_E8044); result = 1LL; lineofsighttoFuncPtr_E9218 = (__int64 (__fastcall *)(_QWORD, _QWORD, _QWORD, long double, long double, long double))(libUE4_E91D0 + 0xXXXXXXX); return result; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 __int64 __fastcall engineFontInit_3F5BC() { v10 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40); gEngine_E9948 = RAM_uintptr_t_3D610(libUE4_E91D0 + gEngineOffset_E8034); TinyFont_E9298 = RAM_uintptr_t_3D610(gEngine_E9948 + 0x30); MediumFont_E92A0 = RAM_uintptr_t_3D610(gEngine_E9948 + 0x70); LargeFont_E9290 = RAM_uintptr_t_3D610(gEngine_E9948 + 0x90); v5 = 16; v8 = &v5; v9 = 4LL; LegacyFontSize = RAM_uintptr_t_3D610(gEngine_E9948 + 0x30) + 0x134; v7 = 4LL; if ( (getpid() & 0x80000000) == 0 ) { v0 = getpid(); syscall(dword_E805C, v0, &v8, 1LL, &v6, 1LL, 0LL); } v4 = 12; v1 = RAM_uintptr_t_3D610(gEngine_E9948 + 0x90); v8 = &v4; v9 = 4LL; v6 = v1 + 0x134; v7 = 4LL; result = getpid(); if ( (result & 0x80000000) == 0 ) { v3 = getpid(); return syscall(dword_E805C, v3, &v8, 1LL, &v6, 1LL, 0LL); } return result; }
myESPLogic_3F934就是外挂插入的绘制逻辑,这个函数太长了就不放了,里面也没啥好看的,就是常规的读世界数组,读一些人物状态,拿人物Vector3坐标,把数据转换成屏幕的Vector2的坐标,调用UE4的Kismet2的绘制函数在引擎层绘制(一般来说用的是Ahud和gameViewPortClient)
0x3 追踪逻辑 他这个hook 的应该是shootWeaponComponent里射击角度逻辑的函数,通过实际实践了一下得出float a4,float a5是子弹的射击角度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 __int64 __fastcall bulletTrack_3F4D8(__int64 a1, __int64 a2, long double a3, float a4, float a5) { if ( byte_E9020 && qword_E9950 ) { v8 = *(float *)&qword_E9998 - *(float *)&qword_E99B8; v9 = *((float *)&qword_E9998 + 1) - *((float *)&qword_E99B8 + 1); *(float *)&a3 = atanf( (float)(*(float *)&dword_E99A0 - *(float *)&dword_E99C0) / sqrtf((float)(v8 * v8) + (float)(v9 * v9))) * -57.296; v10 = atanf(v9 / v8) * 57.296; a4 = v10 + 180.0; if ( v8 < 0.0 ) a4 = v10; a5 = 0.0; } return qword_E9A20(a1, a2, a3, a4, a5); }
0x4 小结 至此,这个样本分析完了,这个so里主要功能没有加密,所以分析起来比较简单,而且这个代码也是越看越眼熟,好像用的是github上的一个CSGOM的模板。如果遇到那种加密和混的比较厉害的,想偷懒的话感觉能先找一些比较常用的函数,例如pthread——create、getnativewindow这种,找到之后从那个函数倒着往回分析。
最近还研究了内核模块通信读取、断点,hook内核函数隐藏信息,编译的时候隐藏符号等,了解了一下比较常见的用户态检测,后面有空的时候也记录一下。