最近看了下"大富翁8",玩了一下。玩的时候想用个修改器改改东西,就粗略的研究了一下。
一、用户内存数据
我使用WinDbg挂住richman8.dat后,通过内存搜索命令查找现金、存储、点券等数据,在内存中定位到对应的用户数据位置。
下面是我找到的
如上找到对应的几个数据,从这可以推断Rich8的用户数据可能在一块,对应着一个数据结构。
二、反汇编相应部分代码
在对应的内存数据(现金、点券等)上设置内存断点。这样代码在访问内存时会断下来,比如在现金上设置写内存断点,这样会在用户金钱变动时断下来,
那么对应的调用代码就是和修改金钱对应的代码。分析断下时的调用堆栈,找出可能相关的代码段。
下面是我分析的,使用的是IDA5.5,richman8.da是使用PECompact 2.x -> Jeremy Collake加壳的。可以使用工具或自己手工脱。
gRichCardsTable, *((_DWORD *)v13 + *((_DWORD *)v13 + 96) + 7));
if ( *(_DWORD *)(gRichDataInfo + 0xC) <= 0 )
{
v24 = 0;
}
else
{
IndexOfUser = *(_DWORD *)(gRichDataInfo + 0x2C);
if ( IndexOfUser >= 0 && *(_DWORD *)(gRichDataInfo + 12) > IndexOfUser )
v25 = *(void **)(*(_DWORD *)gRichDataInfo + 4 * IndexOfUser);
else
v25 = 0;
v24 = v25;
}
v2 = sub_4E6880(v30);
CardAdd(v24, *((_DWORD *)v13 + *((_DWORD *)v13 + 96) + 7), v2);
这里v24是一个玩家信息对象,v2是第几个卡片,第二参数可以不管而v24是从gRichDataInfo这个对象中取得,gRichDataInfo是全局的对象
signed int __thiscall CardAdd(void *this, int a2, int a3)
{
signed int result; // eax@3
int v4; // [sp+0h] [bp-8h]@1
int ValueOfCard; // [sp+4h] [bp-4h]@1 v4 = (int)this;
ValueOfCard = CardValueCalcFromIndex(gRichCardsTable, a2);
if ( ValueOfCard && UserAddCard((void *)(v4 + 692), ValueOfCard, -1) )
{
sub_4898C0(v4, a3, 0);
sub_480A60(v4);
sub_480C00(v4);
result = 1;
}
else
{
result = 0;
}
return result;
}
这里通过CardValueCalcFromIndex函数取得卡片索引对应的实际值。
signed int __thiscall UserAddCard(void *this, int ValueOfCard, signed int IndexOfCard)
{
signed int result; // eax@12
signed int i; // [sp+8h] [bp-8h]@4
signed int v5; // [sp+Ch] [bp-4h]@2 if ( !ValueOfCard )
goto LABEL_16;
v5 = IndexOfCard;
if ( IndexOfCard < 0 || IndexOfCard >= 8 )
{
for ( i = 0; i < 8; ++i )
{
if ( !*((_DWORD *)this + 3 * i) ) // 找到第一个为空的位置
{
v5 = i;
break;
}
}
}
if ( v5 < 0 || v5 >= 8 || *((_DWORD *)this + 3 * v5) )
{
LABEL_16:
result = 0;
}
else
{
*((_DWORD *)this + 3 * v5) = ValueOfCard; //设置卡片的值
*((_DWORD *)this + 3 * v5 + 1) = 0;
CardTotalCountCalc(this);
result = 1;
}
return result;
}
这里就是把卡片对应的这设置到对应的内存位置中
int __thiscall CardTotalCountCalc(int this)
{
int result; // eax@1
signed int i; // [sp+4h] [bp-4h]@1 result = this;
*(_DWORD *)(this + 0x6C) = 0; // 计算总的卡数
for ( i = 0; i < 8; ++i )
{
result = this;
if ( *(_DWORD *)(this + 12 * i) )
{
result = this;
++*(_DWORD *)(this + 0x6C); //卡片数增加
}
}
return result;
}
这里从新统计玩家对应的卡片数量
三、确定内存数据结构
现在根据上面分析出的数据关系,确定对应的数据结构。
这里涉及到3个对象
gRichDataInfo 内存中全局变量
gRichCardsTable 内中所有卡片的表
v24 内存中对应单个玩家的数据
分析他们之间的对应的偏移、包含关系,确定数据结构。
下面是我分析的数据结构
00000000 RICH_DATA_INFO struc ; (sizeof=0x30)
00000000 UserDataArray dd ?
00000004 Reserved1 dd ?
00000008 Reserved2 dd ?
0000000C UserTotalCount dd ?
00000010 Reserved3 db 28 dup(?)
0000002C IndexOfUserSelected dd ?
00000030 RICH_DATA_INFO ends
00000000 RICH_CARD_INFO struc ; (sizeof=0x10)
00000000 CardValueTable dd ?
00000004 Reserved1 dd ?
00000008 Reserved2 dd ?
0000000C CardTotalCount dd ?
00000010 RICH_CARD_INFO ends
00000000 RICH_USER_INFO struc ; (sizeof=0x328)
00000000 Reserved1 db 656 dup(?)
00000290 CashValue dd ?
00000294 SavingsValue dd ?
00000298 Reserved2 db 28 dup(?)
000002B4 ArrayOfUserCard RICH_USER_CARD_ITEM 8 dup(?)
00000314 Reserved3 dd ?
00000318 Reserved4 dd ?
0000031C Reserved5 dd ?
00000320 CountOfUserCard dd ?
00000324 CouponValue dd ?
00000328 RICH_USER_INFO ends
00000000 RICH_USER_CARD_ITEM struc ; (sizeof=0xC)
00000000 CardValue dd ?
00000004 Reserved1 dd ?
00000008 Reserved2 dd ?
0000000C RICH_USER_CARD_ITEM ends
在定义的数据结构中,由于我只需要修改它的现金、储蓄、点券和卡片,所以就只定义了几个我需要的变量,其余的那些如贷款、
状态什么的我就没有去看它对应着哪个,不过估计就在我定义的那些预留变量中。
(头好酸啊,一个晚上就这样过去了...