cocos2d-x游戏逆向分析实践

By xia0

#cocos2d-x游戏逆向分析实践

本文将以一款简单的cocos2d-x的iOS版游戏《PassItThough》来进行简单的逆向分析以及外挂编写

#如何下手?

这是我分析的第一个cocos2d-x的游戏,也是我第一次分析游戏。就我目前的搜索到的资料来看,关于cocos2d-x游戏的逆向分析文章可以说几乎没有,所以我准备写一篇自己在分析cocos2d-x游戏过程中的一些理解和技巧。如何下手确实是我或者大多数刚进行游戏分析的最关键的问题。我会尽可能的描述清楚我从对游戏一无所知到最后成功破解一个游戏的过程,从我学习cocos2d-x游戏框架到最终破解完成耗时差不多6天时间,在这些时间里面前面4天都主要在学习cocos2d-x这个游戏引擎,后面对《PassItThough》游戏的逆向破解反而只用了两天,这里我要说明的一点在于,只有你对这个引擎或者框架足够熟悉了解以后才能有方向的进行逆向,瞎猜或者乱撞运气好可能会成功,但我坚持的观点是逆向不是全靠猜的,而是有方向的。所以如果在看这篇文章之前对cocosd-x这个游戏引擎还不熟悉的话,建议先学习一下这个引擎之后再回来看这篇文章。

#游戏介绍

这是一个十分简单的游戏,如下所示

PassItThough

游戏玩法就是拖动红环到另一端即可进入下一关,默认只有过关以后才能进入下一关,其他关卡都是没有解锁的。但是在拖动过程之中不能触碰到海草,一旦触碰就会游戏失败。

这里有两种破解选择:

  • 忽略海草触碰,不触发游戏失败
  • 解锁关卡,使其能够玩后面的关卡

#逆向分析思路

先说结果,我最终实现的是解锁关卡,再进入一个关卡的时候直接游戏通关,能够解锁下一个关卡。

由于cocos2d-x是c++编写的游戏,在进行逆向的时候如果没有目的盲目的分析,你可能会怀疑人生,拖到ida里面会发现没有函数符号,全是指针偏移等,可以说无从下手。在这里,我们还是从最终目的出发,假设我们想实现触碰海草不会触发游戏失败的外挂,这里应该怎么思考实现这个功能呢?

从我学习cocos2d-x的经验来看,这里需要解决的是如何找到碰撞检测函数,一旦我们找到的话直接patch掉这个函数不就可以实现目的了吗?但是怎么去找这个函数还是十分麻烦的,我的想法是可能这个函数在点击和拖动事件的回调之中,也就是在拖动的过程中去判断环和海草是否碰撞。那么接下来的问题就是怎么找到拖动的回调函数?

熟悉cocos2d-x就知道,一般实现逻辑就是在Scene中注册点击事件回调函数来处理屏幕中的点击事件,通常的代码如下:

void HelloWorld::registerWithTouchDispatcher()
{
    auto touchListener = EventListenerTouchOneByOne::create();
    touchListener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
    touchListener->onTouchMoved = CC_CALLBACK_2(HelloWorld::onTouchMoved, this);
    touchListener->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded,this);
    _eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);

    this->schedule(schedule_selector(HelloWorld::updatePoint));
}

bool HelloWorld::onTouchBegan(Touch *touch,  Event*event)
{
    CCLOG("touch Began.");
    return true;
}
void HelloWorld::onTouchMoved(Touch *touch,  Event*event)
{
       CCLOG("touch moved.");
    // 这里编写碰撞逻辑
}
void HelloWorld::onTouchEnded(Touch *touch,  Event*event)
{
    CCLOG("touch ended.");
}

registerWithTouchDispatcher函数放到Scene的create函数或init函数即可。虽然游戏的符号都是strip的,但是我们还是可以通过C++虚函数表能够拿到一些类的信息,这里我们就去ida中搜索EventListenerTouchOneByOne 会得到如下结果

__const:00000001005DD670 ; `vtable for'cocos2d::EventListenerTouchOneByOne
__const:00000001005DD670 _ZTVN7cocos2d26EventListenerTouchOneByOneE DCQ 0 ; offset to this
__const:00000001005DD678                 DCQ _ZTIN7cocos2d26EventListenerTouchOneByOneE ; `typeinfo for'cocos2d::EventListenerTouchOneByOne
__const:00000001005DD680 off_1005DD680   DCQ sub_100344E10       ; DATA XREF: sub_100344D24+10↑o
__const:00000001005DD680                                         ; sub_100344D24+14↑o ...
__const:00000001005DD688                 DCQ sub_100344E14
__const:00000001005DD690                 DCQ sub_100344FAC
__const:00000001005DD698                 DCQ sub_100344FBC

这里直接看第一个虚函数的交叉引用,因为如果熟悉c++逆向的话就知道,在初始化一个类的时候,紧接着就会将第一个地址出写入虚函数地址表首项地址。拿到了初始化代码的位置,再进行一次交叉引用就能拿到调用这个类的位置。这里第一次交叉引用得到如下

_QWORD *EventListenerTouchOneByOne::create()
{
  __int64 v0; // x0
  _QWORD *v1; // x19

  v0 = operator new(256LL, &std::nothrow);
  v1 = (_QWORD *)v0;
  if ( v0 )
  {
    sub_1003B8154(v0);
    *v1 = off_1005DD6B0;
    v1[19] = 0LL;
    v1[23] = 0LL;
    v1[27] = 0LL;
    v1[31] = 0LL;
    if ( (unsigned int)sub_1003452D4(v1) )
    {
      sub_1003C8778((__int64)v1);
    }
    else
    {
      (*(void (__fastcall **)(_QWORD *))(*v1 + 8LL))(v1);
      v1 = 0LL;
    }
  }
  return v1;

这里的off_1005DD6B0就是虚函数表首地址,可以明显的看出来这是EventListenerTouchOneByOne的构造函数。(所以一些启示就是如果一个类有虚函数,那么在new内存之后马上就会设置虚函数表,所以可以根据此来找到关键代码),找到了构造函数那么我们在进行一次交叉引用去拿到调用的位置

v273 = EventListenerTouchOneByOne::create(v272);
*(_OWORD *)v304 = (unsigned __int64)sub_1000155BC;
v305 = v1;
sub_1000158F0(v273 + 128, v304);
*(_OWORD *)v304 = (unsigned __int64)sub_1000159AC;
v305 = v1;
sub_1000158F0(v273 + 192, v304);
*(_OWORD *)v304 = (unsigned __int64)sub_1000159FC;
v305 = v1;
sub_1000158F0(v273 + 160, v304);
*(_OWORD *)v304 = (unsigned __int64)sub_100015BE8;

这里我们就知道了那个函数就是EventListenerTouchOneByOne::create,所以我对其进行了重命名。再观察下面的代码以及和前面EventListenerTouchOneByOne的代码一结合就知道下面应该是在设置touch的事件,这里有一个比较快速的方法知道点击事件对应的方法,通过对下面的几个函数下断点,然后点击屏幕看断点的触发顺序知道分别对应的点击函数,这里我得到的如下

v273 = EventListenerTouchOneByOne::create(v272);
*(_OWORD *)v304 = (unsigned __int64)onTouchMoved;
v305 = v1;
sub_1000158F0(v273 + 128, v304);
*(_OWORD *)v304 = (unsigned __int64)onTouchEnded;
v305 = v1;
sub_1000158F0(v273 + 192, v304);
*(_OWORD *)v304 = (unsigned __int64)onTouchBegan;
v305 = v1;
sub_1000158F0(v273 + 160, v304);
*(_OWORD *)v304 = (unsigned __int64)j_onTouchBegan;

那么我们现在分析哪一个函数呢,直接点进去分析逆向分析还是很困难,必须缩小范围。接下来我将介绍一种技巧去找到关键函数。

游戏的代码虽然是没有符号的,但是一般资源文件是明文的,我们可以解包以后得到资源,再结合界面信息很容易去找到资源名,然后再ida中搜索资源的代码调用位置就能拿到一些线索。由于这个游戏拖动的是下面的拖动按钮。解包以后发现资源为handle.png在ida中搜索得到以下

__cstring:000000010051314F                                         ; sub_100012134+185C↑o ...
__cstring:0000000100513168 aCommonRing2Png DCB "common/ring2.png",0
__cstring:0000000100513168                                         ; DATA XREF: sub_100012134:loc_100013A3C↑o
__cstring:0000000100513168                                         ; sub_100012134+190C↑o ...
__cstring:0000000100513179 aCommonRingUp2P DCB "common/ring_up2.png",0
__cstring:0000000100513179                                         ; DATA XREF: sub_100012134+1B20↑o
__cstring:0000000100513179                                         ; sub_100012134+1B24↑o ...
__cstring:000000010051318D aCommonHandlePn DCB "common/handle.png",0
__cstring:000000010051318D                                         ; DATA XREF: sub_100012134+1EBC↑o
__cstring:000000010051318D                                         ; sub_100012134+1EC0↑o ...
__cstring:000000010051319F aCommonWinPanel DCB "common/win_panel.png",0

然后交叉引用得到如下

sub_100014CA8(v303, "common/handle.png");
v225 = sub_10039FAD4((__int64)v303);
*(_QWORD *)(v1 + 1120) = v225;
if ( SHIBYTE(v304) & 0x80000000 )
{
  operator delete(v303[0]);
  v225 = *(_QWORD *)(v1 + 1120);
}
sub_1000062CC(v225);
v226 = *(_QWORD *)(v1 + 1120);
(*(void (__fastcall **)(_QWORD))(*(_QWORD *)v226 + 136LL))(*(_QWORD *)(v1 + 1120));
(*(void (__fastcall **)(__int64))(*(_QWORD *)v226 + 128LL))(v226);
v227 = *(_QWORD *)(v1 + 1120);
*(float *)v303 = (float)(*(float *)&dword_1006DDC94 * 30.0) * *(float *)&dword_1006DDCAC;
*((float *)v303 + 1) = *((float *)&qword_1006DDC9C + 1) * 0.37;
(*(void (__cdecl **)(__int64, void **, __int64, __int64))(*(_QWORD *)v227 + 152LL))(v227, v303, v228, v229);
(*(void (__fastcall **)(__int64, _QWORD, signed __int64))(*(_QWORD *)v1 + 528LL))(
  v1,
  *(_QWORD *)(v1 + 1120),
  4LL);

稍加分析就发现,就是将common/handle.png图片初始化为Sprite,也就是说那个按钮就是一个Sprite类,像这种类一般肯定会在Scene中保存其引用也就是这行代码

*(_QWORD *)(v1 + 1120) = v225;

分析这个的原因在于,我们可以去全局搜索调用这个类成员变量的位置,一般对其引用的就是关键代码。但是这里不能直接搜索,只能搜索汇编代码,所以得到这个类对象的汇编代码如下

__text:0000000100013FF0                 ADRP            X1, #aCommonHandlePn@PAGE ; "common/handle.png"
__text:0000000100013FF4                 ADD             X1, X1, #aCommonHandlePn@PAGEOFF ; "common/handle.png"
__text:0000000100013FF8                 ADD             X0, SP, #0x160+var_100
__text:0000000100013FFC                 BL              sub_100014CA8
__text:0000000100014000                 ADD             X0, SP, #0x160+var_100
__text:0000000100014004                 BL              sub_10039FAD4
__text:0000000100014008                 STR             X0, [X19,#0x460]   // 关键点
__text:000000010001400C                 LDRSB           W8, [SP,#0x160+anonymous_0+7]
__text:0000000100014010                 TBZ             W8, #0x1F, loc_100014020
__text:0000000100014014                 LDR             X0, [SP,#0x160+var_100] ; void *
__text:0000000100014018                 BL              __ZdlPv ; operator delete(void *)

这个地址00100014008就是我们需要的代码,x0就是Sprite,保存到Scene的0x460偏移处。既然如此,我们全局搜索#0x460这个偏移,我是写的一个简单的ida Python脚本搜索得到如下结果

----------------- [xda] search-dism-str ----------------
0x100014008    STR             X0, [X19,#0x460]    sub_100012134
0x10001401c    LDR             X0, [X19,#0x460]    sub_100012134
0x100014024    LDR             X20, [X19,#0x460]    sub_100012134
0x10001404c    LDR             X0, [X19,#0x460]    sub_100012134
0x100014084    LDR             X1, [X19,#0x460]    sub_100012134
0x10001560c    LDR             X0, [X19,#0x460]    sub_1000155BC   // 点击回调函数
0x100015644    LDR             X0, [X19,#0x460]    sub_1000155BC
0x100015a54    LDR             X0, [X19,#0x460]    sub_1000159FC   // 点击回调函数
0x100015ad4    LDR             X0, [X19,#0x460]    sub_1000159FC
0x100017564    LDR             X20, [X19,#0x460]    sub_1000174DC
0x100017e3c    LDR             X0, [X19,#0x460]    sub_100017A18
0x100017ec0    LDR             X0, [X19,#0x460]    sub_100017A18
0x100019bbc    STR             X0, [X19,#0x460]    sub_10001934C
0x100019bd0    LDR             X20, [X19,#0x460]    sub_10001934C
0x100019c00    LDR             X0, [X19,#0x460]    sub_10001934C
0x100019c28    LDR             X1, [X19,#0x460]    sub_10001934C
0x100019c40    LDR             X0, [X19,#0x460]    sub_10001934C
0x10001be20    LDR             X1, [X19,#0x460]    sub_10001BDBC
0x10001bf9c    LDR             X0, [X19,#0x460]    sub_10001BDBC
0x100380a18    STR             S0, [X19,#0x460]    sub_10038095C
0x100381c70    LDR             S2, [X19,#0x460]    sub_100381BB0
0x100381cc4    LDR             S2, [X19,#0x460]    sub_100381BB0
0x10039b588    LDR             S0, [X19,#0x460]    sub_10039B540
0x1003a00b4    STR             XZR, [X19,#0x460]    sub_1003A0024
0x1003ead04    LDR             X8, [X19,#0x460]    sub_1003EACC4
0x1003eb58c    LDR             X8, [X19,#0x460]    sub_1003EB560
0x10042a690    STR             W10, [X19,#0x460]    sub_10042A5D8
0x10042a698    LDR             W10, [X19,#0x460]    sub_10042A5D8
0x100458c48    STR             XZR, [X19,#0x460]    sub_100458B74
--------------------------------------------------------

结合之前那几个点击函数,会发现有两处相同的函数,即上面的回调函数。经过分析发现就是onTouchMovedonTouchBeganSprite进行了引用。这也很合理,在这两个时间中肯定会对该Sprite进行操作。所以接下来重点分析这两个函数。不过我对其进行大量分析和调试发现,这两个函数中并没有进行碰撞检测。由于对游戏开发不熟悉,如果不是在拖动事件函数中检测,那又会在哪里检测呢?

#换个思路

前面说到检测碰撞的函数并不在拖动事件函数中,那还可能是怎么检测碰撞的呢?想象一下可能是用一个线程去循环检测是否发生碰撞以及游戏是否结束的。那我们又应该怎么去分析呢?虽然检测是在单独的线程之中,但检测碰撞本身肯定需要对环本身操作,所以用上面同样的方法去找到环的代码调用位置。

环初始化位置

sub_100014CA8(v303, "common/ring2.png");
v145 = sub_10039FAD4((__int64)v303);
if ( SHIBYTE(v304) & 0x80000000 )
  operator delete(v303[0]);
v146 = (_QWORD *)(*(__int64 (__fastcall **)(__int64))(*(_QWORD *)v145 + 360LL))(v145);
sub_1003A9470(&v299, v146);
sub_100014CA8(v303, "common/ring2.png");
sub_1003A9500(&v301);
v147 = sub_10039FB6C((__int64)v303, (__int64)&v301);
v148 = (__int64 *)(v1 + 824);
*(_QWORD *)(v1 + 824) = v147;

根据如下代码拿到在Scene中的偏移

__text:0000000100013ABC                 STR             X0, [X19,#0x338]

然后用脚本去搜索

----------------- [xda] search-dism-str ----------------
0x100009138    MOV             W0, #0x338    sub_100009124
0x100009148    MOV             W1, #0x338; size_t    sub_100009124
0x100013ab8    ADD             X26, X19, #0x338    sub_100012134
0x100013abc    STR             X0, [X19,#0x338]    sub_100012134
0x100013b6c    LDR             X1, [X19,#0x338]    sub_100012134
0x100013c18    LDR             X0, [X19,#0x338]    sub_100012134
0x100013eb0    LDR             X21, [X19,#0x338]    sub_100012134
0x1000150c4    LDR             X21, [X19,#0x338]    sub_100015064
0x100015100    LDR             X21, [X19,#0x338]    sub_100015064
0x1000151b0    LDR             X20, [X19,#0x338]    sub_100015064
0x100015240    LDR             X0, [X19,#0x338]    sub_100015064
0x100015258    LDR             X0, [X19,#0x338]    sub_100015064
0x10001526c    LDR             X0, [X19,#0x338]    sub_100015064
0x100015284    LDR             X0, [X19,#0x338]    sub_100015064
0x100015b14    LDR             X0, [X19,#0x338]    onTouchBegan
0x100015b2c    LDR             X21, [X19,#0x338]    onTouchBegan
0x100015b68    LDR             X21, [X19,#0x338]    onTouchBegan
0x1000175e8    LDR             X20, [X19,#0x338]    sub_1000174DC
0x100017614    LDR             X21, [X19,#0x338]    sub_1000174DC
0x100017650    LDR             X21, [X19,#0x338]    sub_1000174DC
0x100017a3c    LDR             X21, [X0,#0x338]    sub_100017A18
0x100017a78    LDR             X21, [X19,#0x338]    sub_100017A18
0x100017e1c    LDR             X0, [X19,#0x338]    sub_100017A18
0x100017e5c    LDR             X0, [X19,#0x338]    sub_100017A18
0x100017ea0    LDR             X0, [X19,#0x338]    sub_100017A18
0x100017f08    LDR             X1, [X19,#0x338]    sub_100017A18
0x10001a0a8    STR             X0, [X19,#0x338]    sub_10001934C
0x10001a0bc    LDR             X0, [X19,#0x338]    sub_10001934C
0x10001a0c4    LDR             X0, [X19,#0x338]    sub_10001934C
0x10001a0dc    LDR             X20, [X19,#0x338]    sub_10001934C
0x10001a104    LDR             X20, [X19,#0x338]    sub_10001934C
0x10001a12c    LDR             X1, [X19,#0x338]    sub_10001934C
0x10001bc2c    LDR             X20, [X19,#0x338]    sub_10001BBA0
0x10001bec8    LDR             X20, [X19,#0x338]    sub_10001BDBC
0x10001bf10    LDR             X26, [X19,#0x338]    sub_10001BDBC
0x10001c100    LDR             X20, [X19,#0x338]    sub_10001C05C
0x10001d2a8    LDR             X0, [X19,#0x338]    sub_10001D244
0x10001d50c    LDR             X0, [X20,#0x338]    sub_10001D44C
0x1002fa0b8    STR             W9, [X0,#0x338]    sub_1002F4438
0x1002fd610    LDR             W9, [X0,#0x338]    sub_1002FB588
0x1002fd624    STR             W9, [X0,#0x338]    sub_1002FB588
0x1002fd634    LDR             W10, [X0,#0x338]    sub_1002FB588
0x1002fd640    STR             W9, [X0,#0x338]    sub_1002FB588
0x1002fdfe4    LDR             W9, [X0,#0x338]    sub_1002FB588
0x10030ba80    STR             W9, [X0,#0x338]    sub_100309854
0x10030ba90    LDR             W10, [X0,#0x338]    sub_100309854
0x10030d1f4    LDR             W9, [X0,#0x338]    sub_100309854
0x10031115c    LDR             W9, [X0,#0x338]    sub_100310914
0x100311170    STR             W9, [X0,#0x338]    sub_100310914
0x100311180    LDR             W10, [X0,#0x338]    sub_100310914
0x100311198    LDR             W9, [X0,#0x338]    sub_100310914
0x1003111ec    LDR             W9, [X0,#0x338]    sub_100310914
0x100311204    STR             W9, [X0,#0x338]    sub_100310914
0x100311214    LDR             W10, [X0,#0x338]    sub_100310914
0x100311220    STR             W9, [X0,#0x338]    sub_100310914
0x100311230    LDR             W10, [X0,#0x338]    sub_100310914
0x10031123c    STR             W9, [X0,#0x338]    sub_100310914
0x10031124c    LDR             W10, [X0,#0x338]    sub_100310914
0x100311258    STR             W9, [X0,#0x338]    sub_100310914
0x100311268    LDR             W10, [X0,#0x338]    sub_100310914
0x100311274    STR             W9, [X0,#0x338]    sub_100310914
0x100311284    LDR             W10, [X0,#0x338]    sub_100310914
0x100311290    STR             W9, [X0,#0x338]    sub_100310914
0x100311298    LDR             W9, [X0,#0x338]    sub_100310914
0x10034bdd0    LDR             X8, [X8,#0x338]    sub_10034BD50
0x1003597dc    LDR             X8, [X8,#0x338]    sub_1003597B0
0x10035991c    LDR             X8, [X8,#0x338]    sub_1003598E8
0x10035a044    LDR             X8, [X8,#0x338]    sub_100359FEC
0x10037eae4    STR             XZR, [X19,#0x338]    sub_10037EA34
0x10038d3d8    MOV             W0, #0x338    sub_10038D280
0x100392094    LDR             X8, [X8,#0x338]    sub_100392040
0x100392288    STRB            W8, [X20,#0x338]    sub_100392220
0x1003987d4    STR             XZR, [X19,#0x338]    sub_10039872C
0x1003989f4    LDR             X0, [X0,#0x338]    sub_1003989D8
0x100398ec0    LDR             X0, [X19,#0x338]    sub_100398C24
0x100398ed0    LDR             X0, [X19,#0x338]    sub_100398C24
0x100398ee8    LDR             X0, [X19,#0x338]    sub_100398C24
0x100398f18    LDR             X1, [X19,#0x338]    sub_100398C24
0x10039906c    LDR             X0, [X20,#0x338]    sub_10039904C
0x100399078    STR             X19, [X20,#0x338]    sub_10039904C
0x1003994d0    LDR             X0, [X20,#0x338]    sub_100399474
0x1003998a8    LDR             X8, [X19,#0x338]    sub_10039980C
0x10039aaa0    ADD             X22, X19, #0x338    sub_10039AA54
0x10039ac48    ADD             X0, X19, #0x338    sub_10039ABE8
0x10039acf8    ADD             X20, X19, #0x338    sub_10039ACD4
0x1003e42b0    LDR             X1, [X19,#0x338]    sub_1003E41CC
0x1003e42bc    STR             XZR, [X19,#0x338]    sub_1003E41CC
0x100406318    LDR             X8, [X19,#0x338]    sub_1004062EC
0x100407328    STR             X8, [X0,#0x338]    sub_10040728C
0x100407458    STR             X9, [X0,#0x338]    sub_100407368
0x100408428    LDR             X8, [X19,#0x338]    sub_100407990
0x100409c1c    LDR             X8, [X19,#0x338]    sub_100409B58
0x10041265c    LDR             X8, [X20,#0x338]    sub_10041000C
0x100414318    STR             X8, [X19,#0x338]    sub_1004141E0
0x1004217f4    STR             X8, [X19,#0x338]    sub_100421714
0x100423de0    STR             X8, [X19,#0x338]    sub_100423D34
0x10042a724    STR             W8, [X19,#0x338]    sub_10042A5D8
0x10042aa04    ADD             X9, X27, #0x338    sub_10042A8A4
0x10042b548    LDR             W13, [X19,#0x338]    sub_10042B1AC
0x100468170    STR             X8, [X20,#0x338]    sub_1004678F0
0x10046a948    LDR             X10, [X19,#0x338]    sub_100468D4C
0x10046a964    LDR             X9, [X19,#0x338]    sub_100468D4C
0x10046ede4    LDR             X9, [X19,#0x338]    sub_10046ECA0
0x10046fda0    STR             X8, [X19,#0x338]    sub_10046FCB8
0x10047dedc    ADD             X8, X8, #0x338    sub_10047DE00
0x10047e030    ADD             X8, X8, #0x338    sub_10047DE00
0x10047e380    ADD             X2, X2, #0x338    sub_10047DE00
0x10047e9d8    ADD             X2, X2, #0x338    sub_10047DE00
0x100480438    ADD             X2, X2, #0x338    sub_10047DE00
0x1004844a8    MOV             W1, #0x338; size_t    sub_10048445C
0x1004845cc    MOV             W1, #0x338; size_t    sub_10048445C
0x1004847f4    LDR             X11, [X21,#0x338]    sub_1004846C4
0x100484a9c    LDR             X23, [X19,#0x338]    sub_100484A64
0x100484e58    LDR             X20, [X19,#0x338]    sub_100484E30
0x100484fd0    LDR             X21, [X20,#0x338]    sub_100484FA4
0x100485088    LDR             X22, [X19,#0x338]    sub_100485058
0x10048ede8    STR             X10, [X19,#0x338]    sub_10048EC38
0x10049c594    LDR             W13, [X1,#0x338]    sub_10049BAAC
0x10049cdf8    ADD             X25, X20, #0x338    sub_10049CCA8
0x10049d50c    LDR             W12, [X21,#0x338]    sub_10049D46C
0x10049dd94    ADD             X22, X20, #0x338    sub_10049DBDC
0x10049df1c    STR             WZR, [X8,#0x338]    sub_10049DF08
--------------------------------------------------------

这里得到的结果有好多处,但其实没多少函数,我们对这些函数下断点。会发现总会断在如下位置

==========================================xia0LLDB===========================================
  BlockSymbolFile    Not Set The Block Symbol Json File, Try 'sbt -f'
  =============================================================================================
  frame #0: [file:0x100017a18 mem:0x10215ba18] PassItThrough-mobile`Maybe c function? Distance:53664 >= 2500 # Symbol:-[RootViewController didReceiveMemoryWarning] + 53664
  frame #1: [file:0x100343c44 mem:0x102487c44] PassItThrough-mobile`Maybe c function? Distance:28352 >= 2500 # Symbol:-[CDSoundEngineFader _allowableType] + 28352
  frame #2: [file:0x10034bb48 mem:0x10248fb48] PassItThrough-mobile`Maybe c function? Distance:60868 >= 2500 # Symbol:-[CDSoundEngineFader _allowableType] + 60868
  frame #3: [file:0x10034d1e4 mem:0x1024911e4] PassItThrough-mobile`Maybe c function? Distance:66656 >= 2500 # Symbol:-[CDSoundEngineFader _allowableType] + 66656
  frame #4: [file:0x18540b058 mem:0x1d1e9b058] QuartzCore`CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 808 
  frame #5: [file:0x1854d49cc mem:0x1d1f649cc] QuartzCore`display_timer_callback(__CFMachPort*, void*, long, void*) + 276 
  frame #6: [file:0x180e14a94 mem:0x1cd8a4a94] CoreFoundation`__CFMachPortPerform + 192 
  frame #7: [file:0x180e3d0ec mem:0x1cd8cd0ec] CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 60 
  frame #8: [file:0x180e3c7f4 mem:0x1cd8cc7f4] CoreFoundation`__CFRunLoopDoSource1 + 444 
  frame #9: [file:0x180e37384 mem:0x1cd8c7384] CoreFoundation`__CFRunLoopRun + 2088 
  frame #10: [file:0x180e36844 mem:0x1cd8c6844] CoreFoundation`CFRunLoopRunSpecific + 452 
  frame #11: [file:0x1830e5be8 mem:0x1cfb75be8] GraphicsServices`GSEventRunModal + 104 
  frame #12: [file:0x1ae78431c mem:0x1fb21431c] UIKitCore`UIApplicationMain + 216 
  frame #13: [file:0x10000cc00 mem:0x102150c00] PassItThrough-mobile`main + 56
  frame #14: [file:0x1808ec020 mem:0x1cd37c020] libdyld.dylib`start + 4

从堆栈上分析来看,这应该是一个timer事件,也就是会循环执行的一个函数,当游戏进入Scene以后这个函数就会一直循环执行。应该就是我们想找的函数了,接下来就重点分析0x100017a18这个函数。

在分析前,为了更加确实是检测碰撞的函数,我在调试器中动态patch了第一条指令改为ret。发现就算环和海草碰撞甚至脱到外面游戏也不会失败。验证了我们的猜想,但是由于把整个函数干掉了,所以就算拖到末尾也不会游戏通关。所以还得进一步分析,最终我找到一个patch点

__text:0000000100017C50                 FMUL            S2, S2, S3
__text:0000000100017C54                 FADD            S1, S1, S2
__text:0000000100017C58                 FCMP            S0, S1
__text:0000000100017C5C                 B.GT            loc_100017C88
__text:0000000100017C60                 LDR             X0, [X19,#0x340]
__text:0000000100017C64                 LDR             X8, [X0]
__text:0000000100017C68                 LDR             X8, [X8,#0xE0]
__text:0000000100017C6C                 BLR             X8
__text:0000000100017C70                 LDR             S1, [X19,#0x5D0]
__text:0000000100017C74                 LDR             S2, [X21]
__text:0000000100017C78                 FMUL            S2, S2, S8
__text:0000000100017C7C                 FADD            S1, S1, S2
__text:0000000100017C80                 FCMP            S0, S1
__text:0000000100017C84                 B.LE            loc_100017DDC  // patch 为nop
__text:0000000100017C88
__text:0000000100017C88 loc_100017C88                           ; CODE XREF: sub_100017A18+244↑j
__text:0000000100017C88                 MOV             X0, X19
__text:0000000100017C8C                 BL              sub_100357FA8
__text:0000000100017C90                 MOV             X0, X19
__text:0000000100017C94                 BL              sub_100357F94
__text:0000000100017C98                 BL              cocos2d__Director__getInstance__
__text:0000000100017C9C                 LDR             X0, [X0,#0xB0]
__text:0000000100017CA0                 MOV             W2, #0
__text:0000000100017CA4                 MOV             X1, X19
__text:0000000100017CA8                 BL              sub_1003BB150
__text:0000000100017CAC                 BL              getUserDefault
__text:0000000100017CB0                 LDR             X8, [X0]
__text:0000000100017CB4                 LDR             X8, [X8,#8]
__text:0000000100017CB8                 ADRP            X1, #aTotalCompleteC@PAGE ; "total_complete_chapter"
__text:0000000100017CBC                 ADD             X1, X1, #aTotalCompleteC@PAGEOFF ; "total_complete_chapter"
__text:0000000100017CC0                 MOV             W2, #0
__text:0000000100017CC4                 BLR             X8
__text:0000000100017CC8                 STR             W0, [X19,#0x5B8]
__text:0000000100017CCC                 LDR             W8, [X19,#0x5BC]
__text:0000000100017CD0                 ADD             W8, W8, #1
__text:0000000100017CD4                 STR             W8, [X19,#0x5BC]

将那个位置patch为nop以后,发现游戏直接通过了,进入下一关又直接通关,回到主菜单发现后面的游戏关卡也都解锁了。有点暴力,游戏不玩就直接通关了,没啥游戏体验,但终究能够解锁后面的关卡和通关,所以也能接受了。

关于在调试中patch的代码在我写的lldb调试工具中的patcher命令

https://github.com/4ch12dy/xia0LLDB

#总结

这个游戏算是比较完整的分析完了,在这过程之中,学习到了很多,也发现了与一般app逆向的区别。由于是C++代码,使得逆向分析十分困难,只能用一些分析技巧才能找到关键代码。cocos2d-x引擎游戏算是游戏逆向分析里面比较麻烦的一类了,在腾讯那本游戏安全里面针对这种游戏也没有很好地逆向方法,只提及了字符串搜索,更多可能是猜的过程。但就我分析的思路来看,还是有规律和经验可循的。

#Todo

后面可能会做一个对于C++虚函数表恢复的插件以及编写这个游戏的外挂代码

#参考