IMCAFS

Home

2018 tencent tp game security technology competition

Posted by trammel at 2020-03-08
all

I'm very happy to receive the official invitation to write write up. The first question is too simple. As long as CE modifies the camera coordinates to the upper right corner of an OpenGL 3D program, we can see the flag. Here we analyze the advanced version of the second question

Subject Download

PC Game security direction - final question 2 - advanced version can see that this year's topic is a UE4 engine program, and the game competition finally has something to do with the game

First pass

When loading x32dbg, all kinds of key points are broken. The program relies on two DLLs. After all the entries are broken, click the first verification button, and the maln function of tmgs.dll is broken. In fact, a lot of time is spent on the identification of lua function when analyzing this program. The core of function "verify" is as follows: function "verify" calls Lua script for a check, This is the function names marked one by one according to the source code of lua. It took a lot of time. According to the figure above, it can be found that Lua script is in luaopcode. Exporting luaopcode of lua and decompiling it with luadec can get the verification logic. The standard version is basically finished here, but the advanced version has done some small actions. The op_mode of lua has been processed

function_verify function_verify

By searching the keyword "must be a number" to locate the luav ﹐ execute function, we find that opcode has made a lot of changes by comparing it with Lua source code. By comparing a large number of codes of 47 Lua instructions switch one by one, we can get the restore table and write the function de ﹐ OP ﹐ codes

int de_op_codes(int en) { int r = 0; switch (en) { case 0: return 0xfe; case 6u: case 7u: case 0x16u: case 0x1Bu: return 0; case 0x22u: case 0x28u: case 0x29u: case 0x3Cu: return 1; case 0x3Eu: return 2; case 0x3Bu: return 3; case 0x12u: return 4; case 8u: case 0x11u: case 0x17u: case 0x36u: return 5; case 2u: return 6; case 0xDu: return 7; case 0x1Au: return 8; case 1u: return 9; case 0x1Du: return 0xA; case 0x1Fu: return 0xB; case 0xEu: return 0xC; case 0x31u: return 0xD; case 0x2Fu: return 0xE; case 0x1Eu: return 0xF; case 0x15u: return 0x10; case 0x3Au: return 0x11; case 0x13u: return 0x12; case 0x24u: return 0x13; case 0x2Bu: return 0x14; case 0x1Cu: return 0x15; case 0x2Du: return 0x16; case 0x19u: return 0x17; case 0x3Fu: return 0x18; case 0x18u: return 0x19; case 0x33u: return 0x1A; case 0xFu: return 0x1B; case 0x34u: return 0x1C; case 0x20u: return 0x1D; case 5u: case 9u: case 0xAu: case 0x25u: return 0x1E; case 0x30u: return 0x1F; case 0x26u: return 0x20; case 0x35u: return 0x21; case 0x38: return 0x22; case 0x2Au: return 0x23; case 0x23u: case 0x37u: case 0x39u: case 0x3Du: return 0x24; case 0x27u: return 0x25; case 4u: return 0x26; case 0x2Cu: return 0x27; case 0x32u: return 0x28; case 0x21u: return 0x29; case 0x03: return 0x2A; case 0xCu: return 0x2B; case 0x2Eu: return 0x2C; case 0x14u: return 0x2D; case 0xB: return 0x2E; case 0x10: return 46; default: return 0; } }

Write this function into the get ﹣ opcode macro instruction of luadec, recompile and run luadec to restore Lua. The key parts are as follows

It can be seen that the program has been verified by RC4. Since RC4 is a symmetric encryption algorithm, it only needs to encrypt the ciphertext of DST once to get the pass code

The second pass

By setting breakpoints at key places of UE engine, it is found that the second processing logic is mainly inside UE engine. Event distribution of UE4 engine is as follows, and the following functions will mark detailed offsets

#define RESULT_DECL = void*const Z_Param__Result // UObject::ProcessInternal_62E860 void UObject::ProcessInternal( UObject* Context, FFrame& Stack, RESULT_DECL) call eax // UObject::execLet_632310 void UObject::execLet( UObject* Context, FFrame& Stack, RESULT_DECL) call eax // UObject::execContext_6319D0 void UObject::execContext( UObject* Context, FFrame& Stack, RESULT_DECL ) P_THIS->ProcessContextOpcode(Stack, RESULT_PARAM, /*bCanFailSilently=*/ false); // UObject::ProcessContextOpcode_62E0B0 void UObject::ProcessContextOpcode( FFrame& Stack, RESULT_DECL, bool bCanFailSilently ) call eax // UObject::execFinalFunction_631D50 void UObject::execFinalFunction( UObject* Context, FFrame& Stack, RESULT_DECL ) P_THIS->CallFunction( Stack, RESULT_PARAM, (UFunction*)Stack.ReadObject() ); // UObject::CallFunction_628860 void UObject::CallFunction( FFrame& Stack, RESULT_DECL, UFunction* Function ) Function->Invoke(this, Stack, RESULT_PARAM); // Function_Invoke_641570 void UFunction::Invoke(UObject* Obj, FFrame& Stack, RESULT_DECL) return (*Func)(Obj, Stack, RESULT_PARAM);

A complete process:

-> Function_Invoke_641570 -> UObject::ProcessInternal_62E860 -> UObject::CallFunction_628860 -> UObject::ProcessInternal_62E860 -> UObject::execLet_632310 -> UObject::execContext_6319D0 -> UObject::ProcessContextOpcode_62E0B0 -> sub_631DF0 -> UObject::execFinalFunction_631D50 -> UObject::CallFunction_628860 -> Function_Invoke_641570

The core function is in function ﹣ invoke ﹣ 641570. By making a breakpoint for this function, you can record and call the following functions. The address and specific functions of the function are given below

Function: 16D6860 check2_str_to_FString_846860 Function: 16CAF10 check2_GetUnicodeStringLength_83AF10 Function: 16D6860 check2_str_to_FString_846860 Function: 139B530 check2_md5_50B530 Function: 16CAF10 check2_GetUnicodeStringLength_83AF10 ... 此处省略N个无关函数 Function: 139B340 check2_get_a_md5_50B340 # 内置了一个字符串, 获取内置字符串的md5 Function: 16D6860 check2_str_to_FString_846860 Function: 139B530 check2_md5_50B530 Function: 16C5C60 check2_main_835C60 Function: 50B420 fun_showmsg_50B420(&第二关:验证失败,请重试) ... 此处继续省略N个无关函数

The last step in calling the display function to show the pass failure is the core judgment function check2 ﹣ main ﹣ C60. Again, it is check2 ﹣ MD5 ﹣ 50b530. In this step, a string will be calculated as MD5. Check2? MD5? 50b530 internally calls check2? MD5? Calc? 50a800 for real calculation. For check2 ﹣ MD5 ﹣ calc ﹣ 50a800, you can find that the program successively checks

check2_main_835C60 check2_md5_50B530 check2_md5_50B530 check2_md5_calc_50A800 check2_md5_calc_50A800 E0EA72E0E1C1BFFBC26E8B47AD9D809C tencent_mobile_game+-999893888 输入的PASSWORD

These three contents are used to calculate MD5. Continue the single step tracking and find that the program compares the second string MD5 with the third string MD5 in check2 ﹣ main ﹣ C60. Then it is obvious that the key is the second string. The second string Tencent ﹣ mobile ﹣ game + is written in the program - 999893888, which is calculated here

check2_main_835C60 tencent_mobile_game+ -999893888

This topic mainly focuses on the UE4 event distribution process. Of course, if you don't understand anything, IDA findcrypt can do it directly under MD5

Third pass

Algorithm derivation

When it comes to this level, click the button to break down again, or the last DLL. When you enter the ths function, you can see whether it is processed in ph2.dll (the basic version is sub 100363A0 directly, Enter ph2.dll, which is still Lua script. Dump the bin variable script directly. You can see that the script has JT in the header, is byte code generated by using luajit, and decompilates directly using luajit decomp. The compiled code:

ths

In the check function, a virtual machine is generated. The virtual machine code is executed to encrypt the input data (plainbs). After encryption, the virtual machine code is compared with the built-in one (dstres). Through exporting the virtual machine code, the export tool and the exported z3t table.lua have been placed in the appendix. The virtual machine code has 18 instructions, For addition, subtraction, multiplication and division, jump, stack pressing and stack out, etc., the virtual machine code needs to be analyzed. The appendix has been re implemented by myself through JS. In fact, the standard version is almost the same as the advanced version. The code in the appendix includes the implementation of the Standard Version. It can be said that the advanced version is the same except for the BigNumber

The analysis is as follows:

algorithm analysis

Initialize a B64 alphabet first

Then the input data is grouped into 8 groups, and the first two do the following operations to generate 8-byte data

The last six are calculated as follows

InfInt x = (x2 * 256 + x1) + (x3 * 256 * 256) + 256 * 256 * 256 * (x5 * 256 + x4 + x6 * 256 * 256); (实际上是 x1-x6分别为二进制8位排开) a = savebyte + (x % 61454 * 256) b = (x % 54732) + ((x % 5136) % 256 * 256 * 256) c = (x % 25548) * 256 + ((x % 5136) >> 8)

Then generate 4 bytes of target data for a / B / C / (res2), 3 * 4 = 12 bytes in total

Reverse thinking:

Dstres group, each group 8 + 12 = 20 bits, the first 8 is to calculate the original first 2 bits, the last 12 bits to calculate the last 6 bits

Detailed reverse process:

The first two bits can be solved directly with constraints

The last six bit algorithm is more complex and has large data, so it can be simplified first because the calculation process is

x = (x2 * 256 + x1) + (x3 * 256 * 256) + 256 * 256 * 256 * (x5 * 256 + x4 + x6 * 256 * 256) a = savebyte + (x % 61454 * 256) b = (x % 54732) + ((x % 5136) % 256 * 256 * 256) c = (x % 25548) * 256 + ((x % 5136) >> 8)

Therefore, the reverse process is known as ABC. To find x (x1-x6 can be calculated by x), write the formula R=savebyte as

R+(x%61454*256)=a, (x%54732)+((x%5136)%256*256*256)=b, (x%25548)*256+((x%5136)>>8)=c

Simplification:

x%61454 = (a-R) >> 8 x%54732 = b & 0xFFFF x%5136 = ((b & 0xFF0000) >> 16) + ((c & 0xFF) << 8) x%25548 = c >> 8

At this stage, we can see that all the equations on the right are known, which are transformed into congruent equations. Since m is not coprime, we can't use Sun Tzu's theorem, which is the same as the problem project Euler problem 531

对于一个同余方程: 设g=gcd(n1,n2),可以得到若方程有解,则g|(a1−a2) 必成立;其逆否命题成立。 复杂度O(n2logn) 。因此此题中四组可以两两分组 x1 and x2 得到 x',x' and x3 得到 x'',x'' and x4 得到 answer

After calculating x, x1-x6 can be calculated in this way

So far, the three problems have been solved

Appendix code

https://github.com/Tai7sy/mtp_2018_write_up