第一題實在過于簡單,一個OpenGL的3D程序, 只要CE修改camera坐標到右上角就能看到flag了
這里分析第二題的進階版
第一關
x32dbg載入, 各種關鍵點下斷, 程序依賴了兩個dll, 入口全部下斷再說,
果然點擊第一關的驗證按鈕, 斷在了tmgs.dll的maln函數
其實這個程序分析的時候大量時間花在了lua函數的識別上,

function_verify核心如下:

function_verify里面是調用lua腳本進行一個check, 這里是對照lua源碼一個個標記的函數名, 花費了很多時間
同時根據上圖可以發現lua腳本在luaOpcode里面,將lua的luaOpcode導出使用luadec反編譯可以得出驗證邏輯,
標準版到這里基本就結束了,但是進階版官方做了一些小動作,lua的op_mode被做了一些處理
通過搜索關鍵詞”must be a number” 定位到luaV_execute函數,此處通過和lua源代碼對比發現opcode做了大量改動,通過逐一對比47個lua指令switch的大量代碼,人肉得出還原表 寫出函數 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;
}
}
將此函數寫入 luadec的GET_OPCODE宏指令中,重新編譯運行luadec即可還原出lua, 關鍵部分如下

可以看到程序里面通過rc4做了驗證,由于rc4是對稱加密算法,因此只需要對dst的密文加密一次就可以得到通關密碼

第二關
通過在UE引擎關鍵地方下斷點,發現第二問處理邏輯主要在UE引擎內部,
UE4引擎事件分發如下, 下文函數都會標記詳細的偏移
#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);
一個完整流程:
-> 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
核心函數在 Function_Invoke_641570 里面, 通過對此函數進行下斷點可以記錄到調用了以下幾個函數, 下面給出函數地址和具體功能
Function: 16D6860 check2_str_to_FString_846860
Function: 6CAF10 check2_GetUnicodeStringLength_83AF10
Function: 16D6860 check2_str_to_FString_846860
Function: 39B530 check2_md5_50B530
Function: 6CAF10 check2_GetUnicodeStringLength_83AF10
... 此處省略N個無關函數
Function: 39B340 check2_get_a_md5_50B340
# 內置了一個字符串, 獲取內置字符串的md5
Function: 16D6860 check2_str_to_FString_846860
Function: 39B530 check2_md5_50B530
Function: 6C5C60 check2_main_835C60
Function: 0B420 fun_showmsg_50B420(&第二關:驗證失敗,請重試)
... 此處繼續省略N個無關函數
在調用顯示函數顯示出通過失敗的上一步, 就是核心判斷函數check2_main_835C60。
再次向上,是check2_md5_50B530,這一步里面會將一個字符串計算md5。
check2_md5_50B530內部又調用check2_md5_calc_50A800進行真正的計算。
對·check2_md5_calc_50A800·下斷點可以發現程序依次對
E0EA72E0E1C1BFFBC26E8B47AD9D809C
tencent_mobile_game+-999893888
輸入的PASSWORD
這三個內容計算md5,
繼續單步跟蹤發現程序在check2_main_835C60里面對 第二次 和 第三次 字符串md5做對比, 那么key顯而易見了, 就是第二次的字符串
第二次的字符串tencent_mobile_game+是寫死在程序里面的
-999893888 是這里算出來的


本題主要考察UE4事件分發流程了, 當然如果什么都不懂的話直接IDA findcrypt在md5的地方下斷也能做出來就是了...
第三關
算法導出
到了這一關, 點擊按鈕又順利斷下來了, 還是上次的dll, 進入了ths函數

可以看到是在ph2.dll里面進行的處理(基礎版直接是sub_100363A0,和第一關類似的過程)

進入ph2.dll,依然是lua腳本,直接dump出bin變量的腳本,
可以看到腳本是頭部有jt,是使用luajit生成的字節碼,直接使用luajit-decomp進行反編譯, 編譯后代碼:

check函數內生成了一個虛擬機, 執行虛擬機代碼對輸入的數據(plainBs)進行加密處理, 加密后和內置的(dstRes)進行比對
通過對虛擬機代碼進行導出,導出工具以及導出的z3t_table.lua都已經放在附錄,
虛擬機代碼有18個指令, 分別為加減乘除判斷跳轉壓棧出棧等,且采用了大數運算庫
這里需要對虛擬機代碼進行分析, 附錄有我自己通過js自己重新實現的,
其實這里標準版和進階版差不多了, 附錄的代碼里面附帶了標準版的實現, 可以說進階版除了數大一點(BigNumber😄), 其余的全是一樣的
分析后如下:
算法分析
首先初始化一個b64字母表

然后對輸入的數據進行分組,8個一組,前兩個做以下運算, 生成8字節數據

對后6個的運算如下
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)
隨后對a/b/c/(res2)生成4個字節目標數據, 一共3*4=12字節

逆向思路:
dstRes分組,每組8+12=20位,前8為計算出原有前2位,后12位計算出后6位
詳細逆向過程:
前2位可以直接約束求解

后6位算法較為復雜, 且數據較大, 可以先化簡分析
由于計算過程是
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)
因此逆向過程為 abc已知, 求x(x1-x6可以通過x算出)
令R=savebyte
公式寫為
R+(x%61454*256)=a,
(x%54732)+((x%5136)%256*256*256)=b,
(x%25548)*256+((x%5136)>>8)=c
化簡:
x%61454 = (a-R) >> 8
x%54732 = b & 0xFFFF
x%5136 = ((b & 0xFF0000) >> 16) + ((c & 0xFF) << 8)
x%25548 = c >> 8
到這一步可以看出右邊均是已知,轉化為同余方程組,
由于m不互質,因此不可以使用孫子定理, 這就和這一題一樣了
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
算出x之后,x1-x6可以這樣算出

至此,三道題求解完畢

附錄代碼
https://github.com/Tai7sy/mtp_2018_write_up