這陣子無聊在玩肥宅谷私服
想說無聊發一下解析的成果囉
這篇可能會一堆人看無
除了要有逆向基礎
還要碰過 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的資料