摸鱼了好几个月, 期间是有写过文章的不过咕掉了, 搓文章太累了
4月初我hxd介绍了款手游给我 <坎****>, 说来让我碰碰
我一看! 嗷! ObscuredFloat 啊, 游戏概述, 没意思, 骡的岛用的就是这个, 太弱鸡了不搞
然后我就试着玩了一下, 意外的有点上头, 然后就研究了一下伤害的解析
刚一碰, 我大E了啊, 没有闪, 一套不定长内存把我打麻瓜了
我说你这小伙子不讲武德, 来骗, 来偷袭, 我这单身的几十年的老同志
Android 10 aarch64
GDTS国际服: 2.13.1
IDA Pro: 7.2
Dobby: gayhub
映入眼帘的就是 _damage
这个 成员变量
不过类型有点点奇怪, 是 Nullable<T>
类型
再看看偏移, 就更奇怪了 convertAttackTo
这个成员变量的大小跟 _damage
似乎不一样
ん, 确实不一样, 开始头大了
好家伙, 就一个 has_value
成员变量是个 bool
类型以外, 其他信息全部木大
至于为何特化出来的类型大小不一, 因为成员变量 value
的类型为特化类型, 主要还是要看具体特化类型的内存占用大小
这时就引出了一个问题, T value
为何不应该以指针存储呢?
或者说, 所有对象都继承于 Il2cppObject
却不以指针方式存储值而导致长度不定?
老司机可能会发现这几个类型的定义并不是传统的 class
而是 struct
, 并且首个成员变量的偏移量并不是以 0x10
开始(32位为 0x8
, 其实就是Il2cppObject
内部的两个指针), so, struct
的成员变量, 多半没法直接去调用api获取对应的东西
还有一个区别就是, class
的成员变量都是以二级指针形式存储, 指向的是另一块内存, 获取时必须 *
取值一下才能得到目标 Object
而 struct
是直接占用结构体内部, 偏移即可获得目标 Object
这种特性也解释了为何 struct
的成员变量的长度是不同的
也是 struct
的, 成员变量并没有很有用的信息
需要注意的是C#里 byte
类型对应C++内是 char
(8字节长度), 并且 ACTkByte4
也是 struct
结构体, so hiddenValueOldByte4
这个成员变量可以看做 int32_t
类型
怎么Hook我就不贴出来了, 只贴关键代码
随便找个陷阱撞一下
有点丑, 先按照 类型占用大小
去划分一下内存对照吧
可以看得出并不是指向某处的指针, 所以验证了 struct
类型并没有继承 Il2cppObject
的成员变量
其值是 0x80
, 瞄了一下下面的 0x8-0x10
, 确认该数据类型是 8字节
长度
对照一下结构体的定义, 第一个成员变量 DamageType type
, 而 0x80(128)
跟 DamageType::Trap
对应的上
很显然是一枚指针, 在64位下指针的长度是 8字节
对应于结构体中第二个成员变量 IFieldObject sender
一枚指针
对应于结构体中第三个成员变量 IFieldObject target
对应于结构体中第四个成员变量 Nullable<ObscuredFloat> _modifer
由于值为空, 没啥好说明的, 直接跳过
对应于结构体中第五个成员变量 Nullable<ObscuredFloat> _damage
由于 Nullable<T>
的第一个成员变量是 T value
, 所以特化后展开成员变量样子是这样的
C# 中 int
类型占用 4 字节
0x34 - 0x38 对应 int currentCryptoKey
0x38 - 0x3C 对应 int hiddenValue
ACTkByte4
类型可以当做 int32_t
, 也是 4 字节
0x3C - 0x40 对应 ACTkByte4 hiddenValueOldByte4
bool
类型应该占用 1 字节, 但是由于结构体需要 字节对齐, 被拓展成了 4 字节
0x40 - 0x44 对应 bool inited
float
类型占用 4 字节
0x44 - 0x48 对应 float fakeValue
0x48 - 0x4C 对应 bool fakeValueActive
最后的四字节则是 bool has_value
再自行把特化类型的定义抄下来, 就能对Il2cpp中的 Nullable<T>
类型进行操作了
不嫌麻烦的也可以手动展开特化类型的成员变量, 能用就是不够优雅
通过关键词 Decrypt
就能得到几个关键函数, 再过滤一下 形参
跟 返回值
, 传入key的那些就可以无视了
看了一圈最可疑的就这三个函数了
用 IDA 查看一下
均直接调用另一个内部的解密函数
直接Hook sub_2161A10
函数并且调用试试看
先进行定义
hook解密函数, map获取动态库地址的源码太多就不贴上来了
调用解密函数
再去找个陷阱撞一下, 正确识别, 收工
解析B指令获取目标地址然后进行hook
至于怎么解析B指令emmm, 看arm手册就行了, 简单概述的话就是去掉 指令标志位
以后有机会再讲一下吧, 这玩意也是个雷, 去年那篇 Android10 aarch64 dlopen Hook 的解析写法就是有点问题的, 虽然还没触发到那颗雷
点到为止! 点到为止!
已经讲了很多了, 再多说会被 ♂ 的.
搞懂怎么解密后, 加密
其他类型的也是同样的套路
先定义类型, 然后阿吧阿吧阿吧
成员变量自身大小
=
下一个成员变量的偏移
-
自身偏移
Nullable<ObscuredFloat>size:
0x34
-
0x18
=
0x1C
Nullable<ElementalType>size:
0xE4
-
0xDC
=
0x8
成员变量自身大小
=
下一个成员变量的偏移
-
自身偏移
Nullable<ObscuredFloat>size:
0x34
-
0x18
=
0x1C
Nullable<ElementalType>size:
0xE4
-
0xDC
=
0x8
/
/
内存读取
template<typename R>
inline R MemoryRead(void
*
addr, ulong off)
{
return
*
reinterpret_cast<R
*
>((static_cast<char
*
>(addr)
+
off));
}
/
/
xx伤害 函数定义
void (
*
_xxxDamages)(Il2CppObject
*
, void
*
)
=
nullptr;
void xxxDamages(Il2CppObject
*
behaviour, void
*
damageInfo)
{
for
(auto i
=
0
; i <
0x50
; i
+
=
sizeof(void
*
))
LOGE(
"0x%x:\t0x%08"
PRIX32
" - 0x%08"
PRIX32
" - 0x%016"
PRIX64, i,
MemoryRead<int32_t>(damageInfo, i),
MemoryRead<int32_t>(damageInfo, i
+
0x4
),
MemoryRead<int64_t>(damageInfo, i));
return
_xxxDamages(behaviour, damageInfo);
}
/
/
内存读取
template<typename R>
inline R MemoryRead(void
*
addr, ulong off)
{
return
*
reinterpret_cast<R
*
>((static_cast<char
*
>(addr)
+
off));
}
/
/
xx伤害 函数定义
void (
*
_xxxDamages)(Il2CppObject
*
, void
*
)
=
nullptr;
void xxxDamages(Il2CppObject
*
behaviour, void
*
damageInfo)
{
for
(auto i
=
0
; i <
0x50
; i
+
=
sizeof(void
*
))
LOGE(
"0x%x:\t0x%08"
PRIX32
" - 0x%08"
PRIX32
" - 0x%016"
PRIX64, i,
MemoryRead<int32_t>(damageInfo, i),
MemoryRead<int32_t>(damageInfo, i
+
0x4
),
MemoryRead<int64_t>(damageInfo, i));
return
_xxxDamages(behaviour, damageInfo);
}
struct Nullable<ObscuredFloat>
{
int
currentCryptoKey;
int
hiddenValue;
ACTkByte4 hiddenValueOldByte4;
/
/
可以当做int32_t
bool
inited;
float
fakeValue;
bool
fakeValueActive;
bool
has_value;
};
struct Nullable<ObscuredFloat>
{
int
currentCryptoKey;
int
hiddenValue;
ACTkByte4 hiddenValueOldByte4;
/
/
可以当做int32_t
bool
inited;
float
fakeValue;
bool
fakeValueActive;
bool
has_value;
};
/
/
可空类型
template<typename T>
struct Nullable
{
T value;
bool
has_value;
bool
HasValue() const {
return
has_value; }
const T& GetValue() const {
return
value; }
void SetValue(T& newValue) { value
=
newValue; }
};
/
/
可空类型
template<typename T>
struct Nullable
{
T value;
bool
has_value;
bool
HasValue() const {
return
has_value; }
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!