首页
社区
课程
招聘
[原创]UE4逆向笔记之内部绘制
发表于: 2023-1-13 10:24 14022

[原创]UE4逆向笔记之内部绘制

2023-1-13 10:24
14022

UCanvas

在UE4中所有的绘制都是基于UCanvas类的,而我们需要绘制就需要找到UCanvas

寻找UCanvas:

在UE4引擎中有这样一个方法:

1
2
3
4
5
6
7
8
void UGameViewportClient::PostRender(UCanvas* Canvas)
{
#if WITH_EDITOR
    DrawTitleSafeArea(Canvas);
#endif
    // Draw the transition screen.
    DrawTransition(Canvas);
}

调用了DrawTransition,而DrawTransition参数就是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
void UGameViewportClient::DrawTransition(UCanvas* Canvas)
{
    if (bSuppressTransitionMessage == false)
    {
        switch (GetOuterUEngine()->TransitionType)
        {
        case ETransitionType::Loading:
            DrawTransitionMessage(Canvas, NSLOCTEXT("GameViewportClient", "LoadingMessage", "LOADING").ToString());
            break;
        case ETransitionType::Saving:
            DrawTransitionMessage(Canvas, NSLOCTEXT("GameViewportClient", "SavingMessage", "SAVING").ToString());
            break;
        case ETransitionType::Connecting:
            DrawTransitionMessage(Canvas, NSLOCTEXT("GameViewportClient", "ConnectingMessage", "CONNECTING").ToString());
            break;
        case ETransitionType::Precaching:
            DrawTransitionMessage(Canvas, NSLOCTEXT("GameViewportClient", "PrecachingMessage", "PRECACHING").ToString());
            break;
        case ETransitionType::Paused:
            DrawTransitionMessage(Canvas, NSLOCTEXT("GameViewportClient", "PausedMessage", "PAUSED").ToString());
            break;
        case ETransitionType::WaitingToConnect:
            DrawTransitionMessage(Canvas, TEXT("Waiting to connect...")); // Temp - localization of the FString messages is broke atm. Loc this when its fixed.
            break;
        }
    }
}

其中具有很明显的特征ConnectingMessage,直接在xdbg64中搜索该字符串:

 

 

在函数头部下断查看参数:

 

 

该函数存在两个参数,分别是:UGameViewportClient以及UCanvas。

 

将rdx中的值添加到CE进行扫描:

 

 

就成功拿到UCanvas了

利用PostRenderHook进行绘制

在利用PostRenderHook之前我们需要获取到UGameViewportClient指针,对就是刚刚函数中传入的第一个参数。他在:GWorld->Gameinstance->Ulocalplayer->localPlayer->ViewportClient

 

代码中的结构体如下:

 

 

可以看到他和我们函数中的参数值是一样的:

 

 

 

只需要遍历(0x27B45DA7780+0)的值就可以拿到UGameViewportClient所有的虚函数:

1
2
3
4
5
6
7
int method = 0;
 
do
{
    cout << hex << ReadProcess<DWORD64>((PVOID)(UGameViewportClientVtp + (method * 0x8))) << endl;
    ++method;
} while (ReadProcess<DWORD64>((PVOID)(UGameViewportClientVtp + (method * 0x8))));

 

通过反编译后得到了PostRender在地址7ff744219410

 

 

直接修改虚表地址就可以达到HOOK的目的:

1
2
3
4
5
DWORD protecc;
VirtualProtect(&ViewPortClientVTable[RenderIndex], 8, PAGE_EXECUTE_READWRITE, &protecc);
oldPostRender = (tPostRender)ViewPortClientVTable[RenderIndex];
ViewPortClientVTable[RenderIndex] = &PostRender;
VirtualProtect(&ViewPortClientVTable[RenderIndex], 8, protecc, 0);

ProcessEvent:

在Hook完毕PostRender之后还需要找到ProcessEvent的虚表索引,默认情况下都是0x218。但是也有例外,当0x218不是的时候可以上下寻找。实在找不到可以搜索字符串:

1
Did not find a parameter that could receive our value of class

在X64dbg中寻找引用,往上查找call [寄存器+xxx]这样的代码。

 

1
0x210 / 8 = 42

所以ProcessEvent调用代码为:

1
2
3
4
5
void UObject::ProcessEvent(void* fn, void* parms)
{
    auto vtable = *reinterpret_cast<void***>(this);
    reinterpret_cast<void(*)(void*, void*, void*)>(vtable[0x42])(this, fn, parms);
}

K2_DrawText:

K2_DrawText也是原生函数,我们需要借助UE4的反射机制去调用他,不然就需要找到K2_DrawText的函数地址在调用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void UCanvas::K2_DrawText(PVOID RenderFont, const FString& RenderText, FVector2D ScreenPosition, FVector2D Scale, FLinearColor RenderColor, float Kerning, FLinearColor ShadowColor, FVector2D ShadowOffset, bool bCentreX, bool bCentreY, bool bOutlined, FLinearColor OutlineColor)
{
 
    static auto fn = GobjectArray->FindObject("Engine.Canvas.K2_DrawText");
    struct {
        void* RenderFont;
        FString RenderText;
        FVector2D ScreenPosition;
        FVector2D Scale;
        FLinearColor RenderColor;
        float Kerning;
        FLinearColor ShadowColor;
        FVector2D ShadowOffset;
        bool bCentreX;
        bool bCentreY;
        bool bOutlined;
        FLinearColor OutlineColor;
    } parms;
    parms = { RenderFont , RenderText, ScreenPosition, Scale, RenderColor, Kerning, ShadowColor, ShadowOffset, bCentreX, bCentreY, bOutlined, OutlineColor };
    ProcessEvent(fn, &parms);
 
}

关于GobjectArray->FindObject这个方法可以查看我的上一篇文章

在PostRender中调用K2_DrawText:

直接放代码好了,傻子都能看懂

1
2
3
4
5
6
7
8
void PostRender(PVOID UGameViewport, UCanvas* Canvas) {
 
    FVector2D Fv2d{ 300.0f, 300.0f };
    FVector2D Fv2d1{ 1, 1 };
    Canvas->K2_DrawText(defaultFont, L"Hallo World  QQQun: 662851495", Fv2d, Fv2d1, FLinearColor(255, 255, 255, 1.0),1.0f, FLinearColor(0.f, 0.f, 0.f, 1.0f), Fv2d1,true,true,true, FLinearColor(0.f, 0.f, 0.f, 1.0f));
    return oldPostRender(UGameViewport, Canvas);
 
}

效果如下:

 

 

交流群:662851495


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 5
支持
分享
最新回复 (2)
雪    币: 1067
活跃值: (627)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
mark
2023-1-17 13:13
0
雪    币: 1492
活跃值: (1083)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
3
0.0
2023-1-31 10:34
0
游客
登录 | 注册 方可回帖
返回
//