TWMSv113 CashShop Packet Part1

這陣子無聊在玩肥宅谷私服
想說無聊發一下解析的成果囉
這篇可能會一堆人看無
除了要有逆向基礎
還要碰過 Odinms 的JAVA服務端才會大概知道在幹嘛….(菸

進入商城 是屬於 CStage__OnPacket 的一環
負責處理設定進入商城所需要的封包,位置如下面第一篇Code

至於如何找的這個位置的…..說來話長

這個等下次有空寫一篇如何從 GMS 的地址推到 TWMS 的 (汗

 


___:0077BB6C CStage__OnPacket proc near              ; CODE XREF: CField__OnPacket+35Ap
___:0077BB6C                                         ; sub_634677+19Fp
___:0077BB6C                                         ; DATA XREF: ...
___:0077BB6C
___:0077BB6C arg_0           = dword ptr  4
___:0077BB6C Args            = dword ptr  8
___:0077BB6C
___:0077BB6C                 mov     eax, [esp+arg_0]
___:0077BB70                 sub     eax, 7Bh
___:0077BB73                 jz      short loc_77BB97
___:0077BB75                 dec     eax
___:0077BB76                 jz      short loc_77BB89
___:0077BB78                 dec     eax
___:0077BB79                 jnz     short locret_77BBA3
___:0077BB7B                 push    [esp+Args]
___:0077BB7F                 add     ecx, 0FFFFFFF8h
___:0077BB82                 call    CStage__OnSetCashShop
___:0077BB87                 jmp     short locret_77BBA3
___:0077BB89 ; ---------------------------------------------------------------------------
___:0077BB89
___:0077BB89 loc_77BB89:                             ; CODE XREF: CStage__OnPacket+Aj
___:0077BB89                 push    [esp+Args]
___:0077BB8D                 add     ecx, 0FFFFFFF8h
___:0077BB90                 call    CStage__OnSetITC
___:0077BB95                 jmp     short locret_77BBA3
___:0077BB97 ; ---------------------------------------------------------------------------
___:0077BB97
___:0077BB97 loc_77BB97:                             ; CODE XREF: CStage__OnPacket+7j
___:0077BB97                 push    [esp+Args]      ; Args
___:0077BB9B                 add     ecx, 0FFFFFFF8h
___:0077BB9E                 call    CStage__OnSetField
___:0077BBA3
___:0077BBA3 locret_77BBA3:                          ; CODE XREF: CStage__OnPacket+Dj
___:0077BBA3                                         ; CStage__OnPacket+1Bj ...
___:0077BBA3                 retn    8
___:0077BBA3 CStage__OnPacket endp

這個遊戲的封包格式其實很簡單
含有實際使用的封包前面都會跟著 2 Bytes 的 Header
Header包用處在於決定這個封包是做什麼事情的
那這次要說明的進入商城的 Header 為 0x7D

一開始其實不知道是0x7D啦
當初找到這個位置的時候
是因為他只有三個 call 而且 0x7B -> SET_FIELD 這個Header我已知

不過一定有人會說,你知道0x7D,那你怎知道下面兩個的Header勒
所以接下來先來個組合語言小學堂好了

暫存器中,有一種是Flag的暫存器
其中一個叫做 ZF (Zero Flag)
當運算結果為0的時候,ZF會被設定1,反之設定0

這東西在這邊很常見
因為大部分的封包Header判斷都是類似的語句
以上面的那篇Code的例子來解釋:


___:0077BB6C                 mov     eax, [esp+arg_0]

這裡我們不知道 [esp+arg0] 多少,但是他是Header的值,那目前是把這個值儲存到eax中

___:0077BB70                 sub     eax, 7Bh

eax – 0x7B 後在存回eax ,這時候 ZF 值可能就會變動囉

___:0077BB73                 jz      short loc_77BB97

jz 的跳躍條件是 ZF 為 1 的時候,所以 eax 是 0x7B 這個跳躍就成立了

___:0077BB75                 dec     eax 

如果不是 0x7B 就繼續 -1

___:0077BB76                 jz      short loc_77BB89

當這次eax變0後,也就是Header為0x7B(0x7A+1)時,到這裡eax=0,那jz就會被觸發了


___:0077BB78                 dec     eax
___:0077BB79                 jnz     short locret_77BBA3

以此類推…


接下來進入正題, CStage__OnSetCashShop 大概長這樣

我知道很醜,所以我開外掛(被打


___:0077C5A0 CStage__OnSetCashShop proc near         ; CODE XREF: CStage__OnPacket+16p
___:0077C5A0
___:0077C5A0 var_1C          = byte ptr -1Ch
___:0077C5A0 var_18          = dword ptr -18h
___:0077C5A0 var_14          = dword ptr -14h
___:0077C5A0 var_10          = dword ptr -10h
___:0077C5A0 var_C           = dword ptr -0Ch
___:0077C5A0 var_4           = dword ptr -4
___:0077C5A0 arg_0           = dword ptr  8
___:0077C5A0
___:0077C5A0                 mov     eax, offset loc_ADC674
___:0077C5A5                 call    __EH_prolog
___:0077C5AA                 sub     esp, 10h
___:0077C5AD                 push    ebx
___:0077C5AE                 push    esi
___:0077C5AF                 push    edi
___:0077C5B0                 mov     edi, dword_BED280
___:0077C5B6                 xor     ebx, ebx
___:0077C5B8                 lea     ecx, [ebp+var_1C]
___:0077C5BB                 mov     [ebp+var_18], ebx
___:0077C5BE                 call    ZRef_CharacterData____Alloc
___:0077C5C3                 mov     ecx, [ebp+var_18]
___:0077C5C6                 push    ebx
___:0077C5C7                 push    [ebp+arg_0]
___:0077C5CA                 mov     [ebp+var_4], ebx
___:0077C5CD                 call    CharacterData__Decode
___:0077C5D2                 mov     eax, [ebp+var_18]
___:0077C5D5                 push    ecx
___:0077C5D6                 push    ecx
___:0077C5D7                 mov     ecx, esp
___:0077C5D9                 mov     [ebp+var_14], esp
___:0077C5DC                 mov     [ecx+4], eax
___:0077C5DF                 call    sub_42A6FB
___:0077C5E4                 mov     byte ptr [ebp+var_4], bl
___:0077C5E7                 call    sub_42A398
___:0077C5EC                 mov     ecx, eax
___:0077C5EE                 call    CWvsContext__SetCharacterData
___:0077C5F3                 mov     eax, dword_BF34EC
___:0077C5F8                 lea     ecx, [eax+4]
___:0077C5FB                 mov     eax, [ecx]
___:0077C5FD                 push    offset dword_BF2FB4
___:0077C602                 call    dword ptr [eax+48h]
___:0077C605                 test    eax, eax
___:0077C607                 mov     esi, offset dword_BF5D00
___:0077C60C                 jnz     short loc_77C639
___:0077C60E                 push    18h
___:0077C610                 mov     ecx, esi
___:0077C612                 call    ZAllocEx_ZAllocAnonSelector___Alloc
___:0077C617                 mov     ecx, eax
___:0077C619                 mov     [ebp+var_14], ecx
___:0077C61C                 cmp     ecx, ebx
___:0077C61E                 mov     byte ptr [ebp+var_4], 2
___:0077C622                 jz      short loc_77C62B
___:0077C624                 call    sub_5D6A5E
___:0077C629                 jmp     short loc_77C62D
___:0077C62B ; ---------------------------------------------------------------------------
___:0077C62B
___:0077C62B loc_77C62B:                             ; CODE XREF: CStage__OnSetCashShop+82j
___:0077C62B                 xor     eax, eax
___:0077C62D
___:0077C62D loc_77C62D:                             ; CODE XREF: CStage__OnSetCashShop+89j
___:0077C62D                 push    ebx
___:0077C62E                 push    eax
___:0077C62F                 mov     byte ptr [ebp+var_4], bl
___:0077C632                 call    set_stage
___:0077C637                 pop     ecx
___:0077C638                 pop     ecx
___:0077C639
___:0077C639 loc_77C639:                             ; CODE XREF: CStage__OnSetCashShop+6Cj
___:0077C639                 mov     eax, dword_BED280
___:0077C63E                 mov     eax, [eax+3438h]
___:0077C644                 push    1800h
___:0077C649                 mov     ecx, esi
___:0077C64B                 mov     [ebp+var_10], eax
___:0077C64E                 call    ZAllocEx_ZAllocAnonSelector___Alloc
___:0077C653                 mov     ecx, eax
___:0077C655                 mov     [ebp+var_14], ecx
___:0077C658                 cmp     ecx, ebx
___:0077C65A                 mov     byte ptr [ebp+var_4], 3
___:0077C65E                 jz      short loc_77C66A
___:0077C660                 push    [ebp+arg_0]
___:0077C663                 call    CCashShop__CCashShop
___:0077C668                 jmp     short loc_77C66C
___:0077C66A ; ---------------------------------------------------------------------------
___:0077C66A
___:0077C66A loc_77C66A:                             ; CODE XREF: CStage__OnSetCashShop+BEj
___:0077C66A                 xor     eax, eax
___:0077C66C
___:0077C66C loc_77C66C:                             ; CODE XREF: CStage__OnSetCashShop+C8j
___:0077C66C                 lea     ecx, [ebp+var_10]
___:0077C66F                 push    ecx
___:0077C670                 push    eax
___:0077C671                 mov     byte ptr [ebp+var_4], bl
___:0077C674                 call    set_stage
___:0077C679                 or      [ebp+var_4], 0FFFFFFFFh
___:0077C67D                 pop     ecx
___:0077C67E                 mov     dword ptr [edi+2F10h], 1
___:0077C688                 cmp     [ebp+var_18], ebx
___:0077C68B                 pop     ecx
___:0077C68C                 jz      short loc_77C697
___:0077C68E                 push    ebx
___:0077C68F                 lea     ecx, [ebp+var_1C]
___:0077C692                 call    ZRef_CharacterData____Alloc_inner
___:0077C697
___:0077C697 loc_77C697:                             ; CODE XREF: CStage__OnSetCashShop+ECj
___:0077C697                 mov     ecx, [ebp+var_C]
___:0077C69A                 pop     edi
___:0077C69B                 pop     esi
___:0077C69C                 mov     large fs:0, ecx
___:0077C6A3                 pop     ebx
___:0077C6A4                 leave
___:0077C6A5                 retn    4
___:0077C6A5 CStage__OnSetCashShop endp

IDA 外掛開下去之後…


int __stdcall CStage__OnSetCashShop(byte *packet_ptr)
{
  int v1; // ecx@1
  int v2; // eax@1
  int v3; // eax@1
  int v4; // ecx@2
  int v5; // eax@3
  int v6; // ecx@6
  void *v7; // eax@7
  int result; // eax@9
  int v9; // [sp-8h] [bp-30h]@1
  int v10; // [sp-4h] [bp-2Ch]@1
  char v11; // [sp+Ch] [bp-1Ch]@1
  void *v12; // [sp+10h] [bp-18h]@1
  int v13; // [sp+14h] [bp-14h]@1
  int v14; // [sp+18h] [bp-10h]@6
  int v15; // [sp+24h] [bp-4h]@1

  v12 = 0;
  ZRef_CharacterData____Alloc(&v11);
  v15 = 0;
  CharacterData__Decode(v12, (int)packet_ptr, 0);
  v9 = v1;
  v13 = (int)&v9;
  v10 = (int)v12;
  sub_42A6FB(&v9);
  LOBYTE(v15) = 0;
  v2 = sub_42A398();
  CWvsContext__SetCharacterData(v2, v9, v10);
  v3 = *(_DWORD *)(dword_BF34EC + 4);
  v10 = (int)&dword_BF2FB4;
  if ( !(*(int (__stdcall **)(int *))(v3 + 72))(&dword_BF2FB4) )
  {
    v4 = ZAllocEx_ZAllocAnonSelector___Alloc(dword_BF5D00, 0x18u);
    v13 = v4;
    LOBYTE(v15) = 2;
    if ( v4 )
      v5 = sub_5D6A5E(v4);
    else
      v5 = 0;
    LOBYTE(v15) = 0;
    set_stage(v5, 0);
  }
  v14 = *(_DWORD *)(*(_DWORD *)dword_BED280 + 13368);
  v6 = ZAllocEx_ZAllocAnonSelector___Alloc(dword_BF5D00, 0x1800u);
  v13 = v6;
  LOBYTE(v15) = 3;
  if ( v6 )
    v7 = CCashShop__CCashShop((void *)v6, (signed int)packet_ptr);
  else
    v7 = 0;
  LOBYTE(v15) = 0;
  result = set_stage(v7, &v14);
  v15 = -1;
  *(_DWORD *)(*(_DWORD *)dword_BED280 + 12048) = 1;
  if ( v12 )
    result = ZRef_CharacterData____Alloc_inner((int)&v11, 0);
  return result;
}

該命名的我有先命名了一點….其實也才幾行XDDDDD
這個函數呢,就是真正開始解析伺服器傳回來的封包了
所以真正該注意的地方其實只有出現 “packet_ptr” 的地方:
CharacterData__Decode(v12, (int)packet_ptr, 0);
CCashShop__CCashShop((void *)v6, (signed int)packet_ptr);

CharacterData__Decode 是啥呢?

以往不管是GMS,EMS,TWMS
商城封包都會先丟遊戲角色的封包
不過需要這個包滿合理的
楓之谷商城有試穿的功能
那這個階段需要這個也不意外XD
遊戲登入選好伺服器頻道之後
你也會看到這個封包存在
大致上有這些資料:


public static final void addCharacterInfo(final MaplePacketLittleEndianWriter mplew, final MapleCharacter chr) {
        mplew.writeLong(-1);
        mplew.write(0);
        addCharStats(mplew, chr);
        mplew.write(chr.getBuddylist().getCapacity());
        // Bless
        if (chr.getBlessOfFairyOrigin() != null) {
            mplew.write(1);
            mplew.writeMapleAsciiString(chr.getBlessOfFairyOrigin());
        } else {
            mplew.write(0);
        }
        // End
        addInventoryInfo(mplew, chr);
        addSkillInfo(mplew, chr);
        addCoolDownInfo(mplew, chr);
        addQuestInfo(mplew, chr);
        addRingInfo(mplew, chr);
        addRocksInfo(mplew, chr);
        addMonsterBookInfo(mplew, chr);
        chr.QuestInfoPacket(mplew);
        mplew.writeShort(0);
        mplew.writeShort(0);
        mplew.writeShort(0);
    }

接下來看到 CCashShop__CCashShop ,扣很長,所以我拿掉一些不重要的


void *__thiscall CCashShop__CCashShop(void *this, signed byte* packet_ptr)
{
  void *v2; // esi@1

  v2 = this;
  sub_46DD8B((int)this);
 ...
 if ( !CWvsContext__LoadCommodity(*(_DWORD *)dword_BED280) )
  {
    a2 = 570425350;
    _CxxThrowException(&a2, &dword_B289C8);
  }
  CCashShop__LoadData(v2, a2);
  sub_A41B0A(*(void **)dword_BED280);
  *((_DWORD *)v2 + 315) = (unsigned __int8)CInPacket__Decode1(a2);
  return v2;

到這裡我們可以知道 封包最後解析了一個Byte 所以暫時記錄
[data] [1]

跟著封包指標繼續追 CCashShop__LoadData


int __thiscall CCashShop__LoadData(void *this, signed int a2)
{
...
v4 = CInPacket__DecodeStr(packet_ptr, (int)&v64);
v2 = (int)((char *)v3 + 1156);
...
CWvsContext__SetSaleInfo(*(void **)dword_BED280, (int)packet_ptr);
...
CInPacket__DecodeBuffer((int)packet_ptr, (char *)v66 + 64, 1080);
CCashShop__DecodeStock((void *)v46, (int)packet_ptr);
CCashShop__DecodeLimitGoods((void *)v46, (int)packet_ptr);
...

其實到這邊就大概知道樣子了:
[[2(string len)][string bytes]] [Sale data] [1080] [Stock data] [LimitGood Data] [1]

繼續進入 CWvsContext__SetSaleInfo
會看到


...
 v10 = CInPacket__Decode4(a2);
 v41 = v10;
 if ( v10 > 0 )
  {
    sub_47E3E4(v10, &v46);
    CInPacket__DecodeBuffer(a2, Dst, 4 * v10);
  }
  v11 = (unsigned __int16)CInPacket__Decode2(a2);// SpecialItem count
  if ( (signed int)(unsigned __int16)v11 <= 0 )   {     v12 = v48;   }   else   {     i = v11;     do     {       v45 = 0;       sub_48366A(&v44);       LOBYTE(v49) = 2;       v14 = CInPacket__Decode4(a2);             // itemSN       v12 = v48;       v15 = 0;       for ( j = v48 + 4; ; j += 8 )       {         if ( !v48 )           goto LABEL_19;         if ( v15 >= *(_DWORD *)(v48 - 4) )
          goto LABEL_17;
        if ( *(_DWORD *)(*(_DWORD *)j + 12) == v14 )
          break;
        ++v15;
      }
      sub_A428F3(*(_DWORD *)(*((_DWORD *)v3 + 3339) + 8 * v15 + 4));
      CCashShop__decodeModCashItemInfo((void *)v45, a2);
      sub_4749DD(&v44);
      v12 = v48;
...

會看到先解析 2 byte 作為長度
在解析 長度*4 的封包資料
在這邊我是推測他是上架的物品,不過我沒側試過,我現在解決方法是全部塞到接下來這個SpecialItem封包內
SpecialItem 是那些有特殊價錢、期限、等等屬性的商品
這邊的封包也不難
也是2個byte的長度起頭
接著是每個SpecialItem的資訊
先解析了一個 4 bytes 我目前解出他的意義是遊戲資料中的Serail Number , Etc.wz可以查詢到相關的數值
繼續追就來到了 CCashShop__decodeModCashItemInfo

下面我有做註解
基本上就是讀入 4 byte 的 flags
利用int 的32 bits
0x1
0x2
0x4
0x8
0x0C

最後只要做&運算就能知道他存在啥屬性
每個屬性有自己的資料這樣


....
 v4 = this;
  v2 = CInPacket__Decode4(a2);                  // //flags
  v3 = v2;
  if ( v2 & 1 )                                 // itemID
  {
    v5 = CInPacket__Decode4(a2);
    LOBYTE(v2) = sub_42A553(v5);
  }
  if ( v3 & 2 )                                 // count
  {
    LOWORD(v2) = CInPacket__Decode2(a2);
    *((_DWORD *)v4 + 7) = (unsigned __int16)v2;
  }
  if ( v3 & 0x10 )                              // priority
  {
    v2 = (unsigned __int8)CInPacket__Decode1(a2);
    *((_DWORD *)v4 + 10) = (unsigned __int8)v2;
  }
  if ( v3 & 4 )                                 // discount
  {
    v2 = CInPacket__Decode4(a2);
    *((_DWORD *)v4 + 8) = v2;
  }
  if ( v3 & 8 )                                 // unk
  {
    v2 = (unsigned __int8)CInPacket__Decode1(a2);
    *((_DWORD *)v4 + 9) = (unsigned __int8)v2;
  }
  if ( v3 & 0x20 )                              // period
  {
    LOWORD(v2) = CInPacket__Decode2(a2);
    *((_DWORD *)v4 + 11) = (unsigned __int16)v2;
  }
  if ( v3 & 0x40 )
  {                                             // int 0
    v2 = CInPacket__Decode4(a2);
    *((_DWORD *)v4 + 12) = v2;
  }
  if ( v3 & 0x80 )                              // meso
  {
    v2 = CInPacket__Decode4(a2);
    *((_DWORD *)v4 + 13) = v2;
  }
  if ( BYTE1(v3) & 1 )                          // 0x100 unk2
  {
    v2 = (unsigned __int8)CInPacket__Decode1(a2);
    *((_DWORD *)v4 + 14) = (unsigned __int8)v2;
  }
  if ( BYTE1(v3) & 2 )                          // 0x200 gender
  {
    v2 = (unsigned __int8)CInPacket__Decode1(a2);
    *((_DWORD *)v4 + 15) = (unsigned __int8)v2;
  }
  if ( BYTE1(v3) & 4 )                          // 0x400 showUp
  {
    v2 = (unsigned __int8)CInPacket__Decode1(a2);
    *((_DWORD *)v4 + 16) = (unsigned __int8)v2;
  }
  if ( BYTE1(v3) & 8 )
  {
    v2 = (unsigned __int8)CInPacket__Decode1(a2);
    *((_DWORD *)v4 + 17) = (unsigned __int8)v2;
  }
  if ( BYTE1(v3) & 0x10 )
  {
    v2 = (unsigned __int8)CInPacket__Decode1(a2);
    *((_DWORD *)v4 + 18) = (unsigned __int8)v2;
  }
  if ( BYTE1(v3) & 0x20 )
  {
    LOWORD(v2) = CInPacket__Decode2(a2);
    *((_DWORD *)v4 + 19) = (unsigned __int16)v2;
  }
  if ( BYTE1(v3) & 0x40 )
  {                                             // 0
    LOWORD(v2) = CInPacket__Decode2(a2);
    *((_DWORD *)v4 + 20) = (unsigned __int16)v2;
  }
  if ( BYTE1(v3) & 0x80 )                       // meso
  {
    LOWORD(v2) = CInPacket__Decode2(a2);
    *((_DWORD *)v4 + 21) = (unsigned __int16)v2;
  }
  if ( v3 & 0x10000 )
  {
    ZArray_long___RemoveAll((char *)v4 + 92);
    v2 = (unsigned __int8)CInPacket__Decode1(a2);
    if ( (signed int)(unsigned __int8)v2 > 0 )
    {
      v6 = v2;
      do
      {
        v7 = CInPacket__Decode4(a2);
        v2 = ZArray_long___InsertBefore(-1);
        --v6;
        *(_DWORD *)v2 = v7;
      }
      while ( v6 );
    }
  }

然後我懶了….(炸
CCashShop__DecodeStock
CCashShop__DecodeLimitGoods
這兩個比較簡單
進去後發現都是類似這樣


v2 = (unsigned __int16)CInPacket__Decode2(a2);
  if ( v2 > 0 )
  {
    sub_4757CF(v2, 1, &v5);
    CInPacket__DecodeBuffer(a2, *((void **)v3 + 14), 8 * v2);

翻成白話文就是 2個byte的資料長度,後面 長度*8 bytes的資料

留言

comments

發佈留言