[原创]某Pubg Lite辅助逆向分析过程及心得 | 您所在的位置:网站首页 › pubglite教程 › [原创]某Pubg Lite辅助逆向分析过程及心得 |
新的一年,先祝看雪各位成员新年快乐!另外也要多注意身体和卫生,今年与往年大大不同,这次瘟疫大爆发且气象异常搞得人心不安,不要在这样一个欢喜的节日里把自己交代了。在次也希望武汉能挺过这次大劫,武汉加油! 正文:昨天得到一款PubgLite辅助,然后看到一些好玩的功能就尝试着去分析一下,看看能不能为我所用。分析的第一步,当然是先运行查看效果。由于该辅助会被游戏检测,所以只放一张菜单图。
从左上方的菜单我们可以看到有很多功能,首先透视功能基本就是通用公式算出来的,本文主要注重分析其他功能的一个实现。Tip:该游戏位64位游戏,在启动辅助的时候,会加载一个用于读写的驱动,但是火绒报毒说该驱动存在后门,至于真实性不做考察,大家如果想下载来运行一定要注意。 首先PE工具查壳,显示为无壳且32位程序(本次只采用ida原地手撕,至于为什么不用动态调试有两点:1、该辅助实在太lj,开了直接检测,然后秒封号,根本没法调试。2、有后门)。
知道大概信息后,我们直接打开32位的ida,扔进去加载。
老规矩,直接搜索字符串,因为该程序没有加壳,所以搜索到的东西都是明文。 此时我们可以看到字符串中有VMP保护壳的相关字符串,既然前面查壳的时候是无壳,那么这个保护壳,我估计是给驱动文件加的,并不影响我们的分析。我们看看其他内容。
可以看到一些辅助相关的内容。第一张图片可以看看到游戏进程名和类名,还有一些类似特征码和地址、偏移的东西;第二张图就可以看到辅助菜单的内容。那么我们如何通过这些字符串去分析呢?在这里,我给大家普及一下目前市场上辅助大致的绘制流程。
Emmm,有点丑。。。。这张流程图中间的三个模块是在一个死循环里面的。 1)、第一个模块是用来获取游戏数据的,用来对后面功能作为一个初始化。 2)、第二个模块该项功能是整个辅助的核心部分,里面存在多种算法和数据处理,比如透视常用的矩阵算法或者三角函数算法、自瞄用的角度算法、雷达透视用的朝向算法等等,但是这些算法大多都是已经另外封装,这里面只是对其调用。 3)、第三个模块辅助菜单功能模块,就是用来显示和开关用的,里面通常会通过读取控制某个功能开关的变量来判断是否要打开功能。 上面三个模块执行完后就开始一个新的循环。 以上就是目前市场上辅助的大致绘制流程。我们简单了解之后,思考一下怎么去分析,怎么去下手。在上面字符串中我们已经搜索到了菜单的字符串,那么我们就可以直接通过这个字符串来作为突破口。
我们通过ida的交叉引用和F5功能来到了这里,我们大概看了一下,这里面都是菜单内容,而且最让人注意的是它每个功能都会有一个变量来判断开和关(和我上面所说的流程一致)。
这里的标签是我在分析的是注释的,我们现在只需要注意这个地方。我们把这个段代码拉到顶部,也就是函数头的位置,按下X,看看什么地方有调用这个函数。
发现只有一个地方在调用,那么这个地方只可能在绘制流程内。 我们点击确定进行跳转。
我们可以看到我们来到了一坨函数的调用。那么这个地方,就和我说的流程一样了,只不过该辅助呢分的模块比较多。这里我已经分析完了,所以标签和注释什么的都写的一清二楚。接下来将带领大家来分析,看看如何得到这些东西。 我们既然来到了这绘制流程,那么所有的核心代码肯定都在这个部分,我们可以一个一个函数去看,当然,这个只属于有从事这方面开发和有经验的人员,不然还是很难看出来的。那么就代表我们小白脸没法去分析了吗?当然是No。还记得上面菜单模块里面的那些个if吗?
先贴出绘制菜单模块的代码: void DrawMenu() { int v0; // eax LPVOID v1; // eax char *v2; // eax LPVOID v3; // eax LPVOID v4; // ST0C_4 int v5; // ST08_4 int v6; // eax LPVOID v7; // eax char *v8; // eax LPVOID v9; // eax LPVOID v10; // ST0C_4 int v11; // ST08_4 int v12; // eax LPVOID v13; // eax char *v14; // eax LPVOID v15; // eax LPVOID v16; // ST0C_4 int v17; // ST08_4 int v18; // eax LPVOID v19; // eax char *v20; // eax LPVOID v21; // eax LPVOID v22; // ST0C_4 int v23; // ST08_4 int v24; // eax LPVOID v25; // eax char *v26; // eax LPVOID v27; // eax LPVOID v28; // ST0C_4 int v29; // ST08_4 int v30; // eax LPVOID v31; // eax char *v32; // eax LPVOID v33; // eax LPVOID v34; // ST0C_4 int v35; // ST08_4 int v36; // eax LPVOID v37; // eax char *v38; // eax int v39; // eax int v40; // ST0C_4 int v41; // ST08_4 int v42; // eax LPVOID v43; // eax char *v44; // eax LPVOID v45; // eax LPVOID v46; // ST0C_4 int v47; // ST08_4 int v48; // eax LPVOID v49; // eax char *v50; // eax LPVOID v51; // eax LPVOID v52; // ST0C_4 int v53; // ST08_4 int v54; // eax LPVOID v55; // eax char *v56; // eax LPVOID v57; // eax LPVOID v58; // ST0C_4 int v59; // ST08_4 int v60; // eax int v61; // [esp+4h] [ebp-34h] int v62; // [esp+Ch] [ebp-2Ch] int v63; // [esp+Ch] [ebp-2Ch] int v64; // [esp+Ch] [ebp-2Ch] int v65; // [esp+Ch] [ebp-2Ch] int v66; // [esp+Ch] [ebp-2Ch] int v67; // [esp+Ch] [ebp-2Ch] int v68; // [esp+Ch] [ebp-2Ch] int v69; // [esp+Ch] [ebp-2Ch] int v70; // [esp+Ch] [ebp-2Ch] int v71; // [esp+Ch] [ebp-2Ch] LPVOID v72[2]; // [esp+14h] [ebp-24h] double v73; // [esp+1Ch] [ebp-1Ch] LPVOID lpMem[2]; // [esp+24h] [ebp-14h] LPVOID StrSwitch; // [esp+2Ch] [ebp-Ch] int v76; // [esp+30h] [ebp-8h] int v77; // [esp+34h] [ebp-4h] v77 = 10; v76 = 20; StrSwitch = 0; v0 = sub_4065DD(255, 0, 0); lpMem[0] = " T s"; DrawText(lpMem, 10, 20, v0); if ( lpMem[0] ) FreeMem(lpMem[0]); v76 += 25; StrSwitch = 0; lpMem[1] = (LPVOID)sub_4065DD(255, 0, 0); lpMem[0] = 0; HIDWORD(v73) = sub_4065DD(255, 0, 0); if ( Switch_Box ) v1 = lpMem[1]; else v1 = (LPVOID)HIDWORD(v73); v72[1] = "显示方框F1"; DrawText(&v72[1], v77, v76, v1); // 这里是直接把文字显示 if ( v72[1] ) FreeMem(v72[1]); if ( Switch_Box ) // 判断控制该功能的变量是否为真 v2 = "开"; else v2 = "关"; StrSwitch = v2; *(double *)lpMem = (double)v77; v73 = *(double *)lpMem + 75.0; v72[1] = 0; v72[0] = (LPVOID)sub_4065DD(255, 0, 0); v62 = sub_4065DD(255, 0, 0); if ( Switch_Box ) v3 = v72[0]; else v3 = (LPVOID)v62; v4 = v3; v5 = v76; v6 = toInt64(v73); DrawText(&StrSwitch, v6, v5, v4); // 把结果显示出去 if ( StrSwitch ) FreeMem(StrSwitch); v76 += 25; StrSwitch = 0; lpMem[1] = (LPVOID)sub_4065DD(255, 0, 0); lpMem[0] = 0; HIDWORD(v73) = sub_4065DD(255, 0, 0); if ( Switch_Hp ) v7 = lpMem[1]; else v7 = (LPVOID)HIDWORD(v73); v72[1] = "显示血量F2"; DrawText(&v72[1], v77, v76, v7); if ( v72[1] ) FreeMem(v72[1]); if ( Switch_Hp ) v8 = "开"; else v8 = "关"; StrSwitch = v8; *(double *)lpMem = (double)v77; v73 = *(double *)lpMem + 75.0; v72[1] = 0; v72[0] = (LPVOID)sub_4065DD(255, 0, 0); v63 = sub_4065DD(255, 0, 0); if ( Switch_Hp ) v9 = v72[0]; else v9 = (LPVOID)v63; v10 = v9; v11 = v76; v12 = toInt64(v73); DrawText(&StrSwitch, v12, v11, v10); if ( StrSwitch ) FreeMem(StrSwitch); v76 += 25; StrSwitch = 0; lpMem[1] = (LPVOID)sub_4065DD(255, 0, 0); lpMem[0] = 0; HIDWORD(v73) = sub_4065DD(255, 0, 0); if ( Switch_Line ) v13 = lpMem[1]; else v13 = (LPVOID)HIDWORD(v73); v72[1] = "显示射线F3"; DrawText(&v72[1], v77, v76, v13); if ( v72[1] ) FreeMem(v72[1]); if ( Switch_Line ) v14 = "开"; else v14 = "关"; StrSwitch = v14; *(double *)lpMem = (double)v77; v73 = *(double *)lpMem + 75.0; v72[1] = 0; v72[0] = (LPVOID)sub_4065DD(255, 0, 0); v64 = sub_4065DD(255, 0, 0); if ( Switch_Line ) v15 = v72[0]; else v15 = (LPVOID)v64; v16 = v15; v17 = v76; v18 = toInt64(v73); DrawText(&StrSwitch, v18, v17, v16); if ( StrSwitch ) FreeMem(StrSwitch); v76 += 25; StrSwitch = 0; lpMem[1] = (LPVOID)sub_4065DD(255, 0, 0); lpMem[0] = 0; HIDWORD(v73) = sub_4065DD(255, 0, 0); if ( Switch_Aim ) v19 = lpMem[1]; else v19 = (LPVOID)HIDWORD(v73); v72[1] = "右键自瞄F4"; DrawText(&v72[1], v77, v76, v19); if ( v72[1] ) FreeMem(v72[1]); if ( Switch_Aim ) v20 = "开"; else v20 = "关"; StrSwitch = v20; *(double *)lpMem = (double)v77; v73 = *(double *)lpMem + 75.0; v72[1] = 0; v72[0] = (LPVOID)sub_4065DD(255, 0, 0); v65 = sub_4065DD(255, 0, 0); if ( Switch_Aim ) v21 = v72[0]; else v21 = (LPVOID)v65; v22 = v21; v23 = v76; v24 = toInt64(v73); DrawText(&StrSwitch, v24, v23, v22); if ( StrSwitch ) FreeMem(StrSwitch); v76 += 25; StrSwitch = 0; lpMem[1] = (LPVOID)sub_4065DD(255, 0, 0); lpMem[0] = 0; HIDWORD(v73) = sub_4065DD(255, 0, 0); if ( Switch_Breath ) v25 = lpMem[1]; else v25 = (LPVOID)HIDWORD(v73); v72[1] = "开启屏息F5"; DrawText(&v72[1], v77, v76, v25); if ( v72[1] ) FreeMem(v72[1]); if ( Switch_Breath ) v26 = "开"; else v26 = "关"; StrSwitch = v26; *(double *)lpMem = (double)v77; // v78=10.0f v73 = *(double *)lpMem + 75.0; v72[1] = 0; v72[0] = (LPVOID)sub_4065DD(255, 0, 0); v66 = sub_4065DD(255, 0, 0); if ( Switch_Breath ) v27 = v72[0]; else v27 = (LPVOID)v66; v28 = v27; v29 = v76; v30 = toInt64(v73); DrawText(&StrSwitch, v30, v29, v28); if ( StrSwitch ) FreeMem(StrSwitch); v76 += 25; StrSwitch = 0; lpMem[1] = (LPVOID)sub_4065DD(255, 0, 0); lpMem[0] = 0; HIDWORD(v73) = sub_4065DD(255, 0, 0); if ( Switch_HightJump ) v31 = lpMem[1]; else v31 = (LPVOID)HIDWORD(v73); v72[1] = "人物高跳F6"; DrawText(&v72[1], v77, v76, v31); if ( v72[1] ) FreeMem(v72[1]); if ( Switch_HightJump ) v32 = "开"; else v32 = "关"; StrSwitch = v32; *(double *)lpMem = (double)v77; v73 = *(double *)lpMem + 75.0; v72[1] = 0; v72[0] = (LPVOID)sub_4065DD(255, 0, 0); v67 = sub_4065DD(255, 0, 0); if ( Switch_HightJump ) v33 = v72[0]; else v33 = (LPVOID)v67; v34 = v33; v35 = v76; v36 = toInt64(v73); DrawText(&StrSwitch, v36, v35, v34); if ( StrSwitch ) FreeMem(StrSwitch); v76 += 25; StrSwitch = 0; lpMem[1] = (LPVOID)sub_4065DD(255, 0, 0); lpMem[0] = 0; HIDWORD(v73) = sub_4065DD(255, 0, 0); if ( Switch_CarFly ) v37 = lpMem[1]; else v37 = (LPVOID)HIDWORD(v73); v72[1] = "车辆飞行F7"; DrawText(&v72[1], v77, v76, v37); if ( v72[1] ) FreeMem(v72[1]); if ( Switch_CarFly ) v38 = "开"; else v38 = "关"; StrSwitch = v38; lpMem[1] = (LPVOID)sub_40D2AF(1, dword_4DF3E8); lpMem[0] = (LPVOID)sub_40204B((char)StrSwitch); if ( StrSwitch ) FreeMem(StrSwitch); if ( lpMem[1] ) FreeMem(lpMem[1]); v73 = (double)v77; *(double *)v72 = v73 + 75.0; v68 = sub_4065DD(255, 0, 0); v61 = sub_4065DD(255, 0, 0); if ( Switch_CarFly ) v39 = v68; else v39 = v61; v40 = v39; v41 = v76; v42 = toInt64(*(double *)v72); DrawText(lpMem, v42, v41, v40); if ( lpMem[0] ) FreeMem(lpMem[0]); v76 += 25; StrSwitch = 0; lpMem[1] = (LPVOID)sub_4065DD(255, 0, 0); lpMem[0] = 0; HIDWORD(v73) = sub_4065DD(255, 0, 0); if ( Switch_SuperSpeed ) v43 = lpMem[1]; else v43 = (LPVOID)HIDWORD(v73); v72[1] = "人物加速F8"; DrawText(&v72[1], v77, v76, v43); if ( v72[1] ) FreeMem(v72[1]); if ( Switch_SuperSpeed ) v44 = "开"; else v44 = "关"; StrSwitch = v44; *(double *)lpMem = (double)v77; v73 = *(double *)lpMem + 75.0; v72[1] = 0; v72[0] = (LPVOID)sub_4065DD(255, 0, 0); v69 = sub_4065DD(255, 0, 0); if ( Switch_SuperSpeed ) v45 = v72[0]; else v45 = (LPVOID)v69; v46 = v45; v47 = v76; v48 = toInt64(v73); DrawText(&StrSwitch, v48, v47, v46); if ( StrSwitch ) FreeMem(StrSwitch); v76 += 25; StrSwitch = 0; lpMem[1] = (LPVOID)sub_4065DD(255, 0, 0); lpMem[0] = 0; HIDWORD(v73) = sub_4065DD(255, 0, 0); if ( Switch_NoRecoil ) v49 = lpMem[1]; else v49 = (LPVOID)HIDWORD(v73); v72[1] = "腰射午后F9"; DrawText(&v72[1], v77, v76, v49); if ( v72[1] ) FreeMem(v72[1]); if ( Switch_NoRecoil ) v50 = "开"; else v50 = "关"; StrSwitch = v50; *(double *)lpMem = (double)v77; v73 = *(double *)lpMem + 75.0; v72[1] = 0; v72[0] = (LPVOID)sub_4065DD(255, 0, 0); v70 = sub_4065DD(255, 0, 0); if ( Switch_NoRecoil ) v51 = v72[0]; else v51 = (LPVOID)v70; v52 = v51; v53 = v76; v54 = toInt64(v73); DrawText(&StrSwitch, v54, v53, v52); if ( StrSwitch ) FreeMem(StrSwitch); v76 += 25; StrSwitch = 0; lpMem[1] = (LPVOID)sub_4065DD(255, 0, 0); lpMem[0] = 0; HIDWORD(v73) = sub_4065DD(255, 0, 0); if ( Switch_AbsorbObject ) v55 = lpMem[1]; else v55 = (LPVOID)HIDWORD(v73); v72[1] = "超级吸人F10"; DrawText(&v72[1], v77, v76, v55); if ( v72[1] ) FreeMem(v72[1]); if ( Switch_AbsorbObject ) v56 = "开"; else v56 = "关"; StrSwitch = v56; *(double *)lpMem = (double)v77; v73 = *(double *)lpMem + 75.0; v72[1] = 0; v72[0] = (LPVOID)sub_4065DD(255, 0, 0); v71 = sub_4065DD(255, 0, 0); if ( Switch_AbsorbObject ) v57 = v72[0]; else v57 = (LPVOID)v71; v58 = v57; v59 = v76; v60 = toInt64(v73); DrawText(&StrSwitch, v60, v59, v58); if ( StrSwitch ) FreeMem(StrSwitch); }我们可以看到,这部分只有绘制菜单的代码,而且每一个子菜单的代码部分都是基本相同。
这部分的if只是用来判断和显示功能是否打开,里面并未存在任何其他代码,说明真正的功能开关在别处,而且在功能开启时菜单的内容也有所改变,所以一定会修改到控制变量。我们直接在控制变量上按下X,看看哪里有对他赋值或者判断。(我这用内存屏息作为例子,因为涉及到透视的功能存在算法会稍微复杂)
我们看到该处有多个交叉引用,我们发现后面三个都是在我们这一个函数内,所以不用看。主要就看前三个,第一个时进行判断,第二第三个存在于同一个函数内,而且时相邻代码,可以知道他这里是修改了该控制变量,我们直接跳过去看看。
我们发现这里面都是对所有功能的控制变量进行一个取反操作。那我们就知道这个函数只用于对控制变量的一个赋值,我们在函数头交叉引用看看。
发现还是来到这个地方,那么我们就已经分析出两个函数了。现在我们返回对控制变量的交叉引用,我们这次看cmp的那个语句。
我们直接跳转到了这里,很显然这里基本上就是对功能的一个开关。他这里判断如果控制变量为=1就执行。其实这里如果对易语言熟悉的人,脑海中基本可以浮现出这一段所对应易语言代码。 他首先是对14D8这样一个临时进行一个十六进制到十进制的转换,然后对其临时字符串释放(这个也是我在本次逆向分析的时候学到的)。其实他这一句,可能就对应着易语言的进制_十六到十()的这个命令。继续转换之后就开始地址之间的加法,这里也要注意的是,由于程序位32位,游戏为64位,所以指针大小也不同(32位4字节,64位8字节)。Ida这里直接用double计算(一个double 8字节),然后再转为int64也是我这次学到的。最后进行读写操作。这里他是驱动读写,并且在分析的时候发现如果是+44(十进制)那么就为读操作,如果是+64(十进制)就为写操作。大概了解之后,这个函数的代码基本就可以阅读完了。都是对地址+偏移后读,然后再内容。这里就顺便看一下他的屏息如何实现的。 if ( Switch_Breath == 1 ) // 开启屏息 { lpMem = "14D8"; v22 = HexToDec(&lpMem); if ( lpMem ) FreeMem(lpMem); v21 = (double)MySelftAddr; v20 = (double)v22; v19 = v21 + v20; // 本人地址+0x14D8 v0 = toInt64(v19); // 转成int64 LODWORD(LayerOne) = (*(int (__stdcall **)(LPVOID *, int, _DWORD, _DWORD))(*(_DWORD *)dword_4DF300 + 44))( &dword_4DF300, dword_4DF2F4, v0, HIDWORD(v0)); // 读一次 v26 = LayerOne; lpMem = "458"; v22 = HexToDec(&lpMem); if ( lpMem ) FreeMem(lpMem); v21 = (double)v26; v20 = (double)v22; v19 = v21 + v20; // 把读出来的内容再加上0x458,此时得到的这个地址为目标地址 v26 = toInt64(v19); lpMem = &v18; v2 = sub_40D2C1(28, 2); // 不晓得干嘛 if ( lpMem != &v18 ) sub_40D2BB((LPCSTR)6); HIDWORD(v21) = v2; if ( v2 ) (*(void (__stdcall **)(LPVOID *, int, _DWORD, _DWORD, _DWORD))(*(_DWORD *)dword_4DF300 + 64))( &dword_4DF300, dword_4DF2F4, v26, HIDWORD(v26), 0); lpMem = &v18; // 对目标地址写0 v3 = sub_40D2C1(28, 2); if ( lpMem != &v18 ) sub_40D2BB((LPCSTR)6); HIDWORD(v21) = v3; if ( !v3 ) (*(void (__stdcall **)(LPVOID *, int, _DWORD, _DWORD, signed int))(*(_DWORD *)dword_4DF300 + 64))( &dword_4DF300, dword_4DF2F4, v26, HIDWORD(v26), 0x3F000000); // 对目标地址写0.5 }首先是获取一个对象地址,然后让该地址+0x14d8第一层,再加上0x458得到目标地址,最后对该地址写入0就实现了屏息。其他功能都是一样的,现在我们返回函数头部按下X跳出去看看。 我们发现跳出来了还是这个地方。现在我们又分析完了一个,那么现在怎么去分析其他函数呢? 还记得这里吗,它这个数据怎么来我们还不知道,但是我们知道他肯定也是绘制循环里的,而且肯定是本人对象(屏息就是我们自身的呼吸,所以肯定是在本人对象下的),因为他要时时更新游戏数据。我们看看交叉引用,找什么地方给他赋值的。 中间的是我们这个功能函数里的,并不是我们要找的。我们就看第一个我们来到这,发现他来源另一个值,我们看看另一个值怎么来。 我们发现它来源于V5,而v5又是读一个值得到的,那么再往上看,就会发现这里其实就是再遍历对象数组。
64位一个地址相差刚刚好是0x8。知道这个后,这一段的代码其实已经很清晰,首先去遍历对象数组,然后去判断血量是否>0和 v3 ) break; if ( dword_4DF39C >= v69 ) { *(double *)lpMem = (double)v69; // 遍历 v4 = toInt64((double)ActorArray + (*(double *)lpMem - 1.0) * 8.0);// actor+(i-1)*8 LODWORD(v5) = (*(int (__stdcall **)(LPVOID *, int, _DWORD, _DWORD, int, int *, _DWORD))(*(_DWORD *)dword_4DF300 + 44))( &dword_4DF300, dword_4DF2F4, v4, HIDWORD(v4), v56, v2, HIDWORD(v57)); // 读取对象 Object = v5; lpMem[1] = "A90"; // 0xA90 血量偏移 lpMem[0] = (LPVOID)HexToDec(&lpMem[1]); if ( lpMem[1] ) FreeMem(lpMem[1]); v57 = toInt64((double)Object + (double)(signed int)lpMem[0]);// 得到血量地址 v56 = dword_4DF2F4; v67 = COERCE_FLOAT((*(int (__stdcall **)(LPVOID *))(*(_DWORD *)dword_4DF300 + 0x3C))(&dword_4DF300));// 读取血量 *(double *)lpMem = v67; v6 = v67 > 0.0000001 && v67 - 100.0 0 且 血量 |
CopyRight 2018-2019 实验室设备网 版权所有 |