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内核函数隐藏信息,编译的时候隐藏符号等,了解了一下比较常见的用户态检测,后面有空的时候也记录一下。