目录

问道手游中anglescript脚本引擎学习与研究

该手游使用的脚本引擎为anglescript和lua。不同于lua,anglescript脚本引擎在google和baidu上基本找不到资料,抱着学习的目的,写下这篇文章。

关键的加密算法在anglescript的as脚本中,名称为data.cas,是一个编译后的字节码文件。关于anglescript的介绍请浏览官网。

学习与研究

拿到这款手游apk后,先看看是否加固,用了哪个脚本引擎。搜索一下字符串查找游戏发包位置,从so里面了解到,发包函数有可能在lua中或者anglescript中。具体在哪里还不好确定,我们可以dump出脚本。

先别急着动态调试,往往在收集信息的过程中有意想不到的惊喜。我们看下ios版本,用frida-ios-dump工具砸壳。解压出来发现了大量没有加密的lua脚本,从lua脚本分析结果来看发包函数在lua中,数据包的加密算法在anglescript中,加密函数为gfParseKgfParseK

通过简单的分析,基本上确定了加密算法的位置,还需要动态调试验证一下。

接下来我们从官网下载anglescript的sdk,随便下载一个版本,主要用来学习,找找有用的函数。因为我们还无法确定问道中是用了哪一个版本的anglescript。

  • 学习anglescript

官网下载sdk,导入到clion中。简单的浏览源码,发现了几个关键的函数

1
2
3
4
5
6
// 配置engine,用来配置anglescript用到的函数签名、模版类型等等。
// 也就是说在so里面也会用到这个函数配置engine。这真的是个好消息,距离成功又近了一步。
int ConfigureEngine(asIScriptEngine *engine, const char *configFile);

// as脚本加载之后会编译成cas字节码文件,下面的函数就是用来加载字节码文件。这对我们来说很有用,因为我们可以dump出解密后的cas字节码文件,用下面的函数自己加载。
int LoadScript(asIScriptEngine *engine, const char *scriptFile);

接下来我们继续分析anglescript汇编指令的执行细节,从as_restore.cpp中了解到,指令+参数的总长度4字节对齐,其中指令的长度为2字节。as_bytecode.cpp中的DebugOutput函数会打印执行的汇编指令。要打印data.cas中所有的函数签名则可以调用以下流程engine->GetModule()->GetFunctionByIndex()->GetDeclaration()

从打印的函数签名中,我们找到了gfParseKgfParseK函数,证明了上面的分析没有错,接下来我们需要做一个测试验证我们的猜想:加载data.cas,调用gfParseK函数。

前面我们提到了,加载data.as脚本,调用脚本中的函数。需要注册大量的C++函数。重新实现这些函数,很麻烦。

漫漫长征路

我们用到了以下工具:

  1. frida
  2. ida
  3. android studio

从so里面dump出解密后的data.cas

  • 从anglescript sdk中我们学习到一个函数CScriptBuilder::LoadByteCode,该函数从内存中加载字节码文件。所有我们用frida进行hook,成功导出了data.cas。

dump出config文件

  • 同样我们用frida 调用函数WriteConfigToFile,实验中发现无法调用。 所以采用android inline hook的方式进行调用,先hookCScriptBuilder::StartNewModule获取到engine,然后hookCScriptBuilder::LoadByteCode,在这里调用WriteConfigToFile函数成功输出config文件

到这里我们的准备工作算是做完了,剩下就差将字节码反汇编成汇编指令了。要完成转译工作,仍然是学习anglescript sdk源码。具体过程如下:

  1. 创建engineasIScriptEngine *engine = asCreateScriptEngine()
  2. 配置configConfigureEngine(engine, "config.txt");
  3. 开启一个modulebuilder.StartNewModule(engine, "mymodule");
  4. 加载字节码module->LoadByteCode()
  5. 通过module打印所有函数签名module->GetFunctionCount(),module->GetFunctionByIndex(i)->GetDeclaration

asIScriptFunction保存了一个函数的所有信息。通过这个结构可以获取到该函数的所有汇编指令,指令同样用一个结构体保存asSBCInfo

as_context.cppexecute函数中我们找到了最终汇编指令执行的过程,并且DebugOutput中也存在字节码转译汇编指令的过程。

编写代码翻译函数中的所有asSBCInfo,我们很轻松的得到了gfParseKgfParseK函数的汇编指令,然后参考官方的文档还原出了这两个函数的C语言版本。

讨论

在整个过程中,很清晰能体会到的问题是脚本引擎的代码保护强度非常薄弱,lua脚本在ios中直接就是明文了,虽然anglescript进行了加密,但是并不能很好的隐藏关键代码,反而是协议部分相对复杂,有种舍本逐末的感觉。

最后

附上两个函数的汇编指令:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
uint gfParseM(uint m, uint a)
Variables: 
- uint m
- uint a
- uint8 i1
- uint8 i2
- uint8 m1
- uint8 m2
- uint r
Code:
@0x0000 SUSPEND 
@0x0001 SetV4 v2, 0xff0000          (i:16711680, f:16711680)
@0x0003 BAND v2, v65535, v2
@0x0005 SetV4 v3, 0x10          (i:16, f:16)
@0x0007 BSRL v2, v2, v3
@0x0009 iTOb v2
@0x000a CpyVtoV4 v1, v2
@0x000c SUSPEND 
@0x000d SetV4 v3, 0xff          (i:255, f:255)
@0x000f BAND v2, v65535, v3
@0x0011 iTOb v2
@0x0012 CpyVtoV4 v4, v2
@0x0014 SUSPEND 
@0x0015 SetV4 v3, 0xff00          (i:65280, f:65280)
@0x0017 BAND v2, v0, v3
@0x0019 SetV4 v6, 0x8          (i:8, f:8)
@0x001b BSRL v3, v2, v6
@0x001d iTOb v3
@0x001e CpyVtoV4 v5, v3
@0x0020 SUSPEND 
@0x0021 SetV4 v6, 0xff          (i:255, f:255)
@0x0023 BAND v3, v0, v6
@0x0025 iTOb v3
@0x0026 CpyVtoV4 v7, v3
@0x0028 SUSPEND 
@0x0029 CpyVtoV4 v6, v5
@0x002b ubTOi v6
@0x002c CpyVtoV4 v3, v1
@0x002e ubTOi v3
@0x002f BXOR v2, v6, v3
@0x0031 CpyVtoV4 v8, v4
@0x0033 ubTOi v8
@0x0034 BXOR v3, v2, v8
@0x0036 iTOb v3
@0x0037 CpyVtoV4 v5, v3
@0x0039 SUSPEND 
@0x003a CpyVtoV4 v8, v7
@0x003c ubTOi v8
@0x003d CpyVtoV4 v3, v4
@0x003f ubTOi v3
@0x0040 BXOR v2, v8, v3
@0x0042 iTOb v2
@0x0043 CpyVtoV4 v7, v2
@0x0045 SUSPEND 
@0x0046 CpyVtoV4 v3, v5
@0x0048 ubTOi v3
@0x0049 CpyVtoV4 v9, v3
@0x004b SUSPEND 
@0x004c SetV4 v2, 0x8          (i:8, f:8)
@0x004e BSLL v3, v9, v2
@0x0050 CpyVtoV4 v6, v7
@0x0052 ubTOi v6
@0x0053 BOR v2, v3, v6
@0x0055 CpyVtoR4 v2
@0x0056 RET 2


uint gfParseK(uint k, uint a)
Variables: 
- uint k
- uint a
- uint8 i1
- uint8 i2
- uint8 i3
- uint8 i4
Code:
@0x0000 SUSPEND 
@0x0001 SetV4 v2, 0x18          (i:24, f:24)
@0x0003 BSRL v2, v0, v2
@0x0005 iTOb v2
@0x0006 CpyVtoV4 v1, v2
@0x0008 SUSPEND 
@0x0009 SetV4 v2, 0xff0000          (i:16711680, f:16711680)
@0x000b BAND v2, v0, v2
@0x000d SetV4 v4, 0x10          (i:16, f:16)
@0x000f BSRL v2, v2, v4
@0x0011 iTOb v2
@0x0012 CpyVtoV4 v3, v2
@0x0014 SUSPEND 
@0x0015 SetV4 v4, 0xff00          (i:65280, f:65280)
@0x0017 BAND v2, v0, v4
@0x0019 SetV4 v6, 0x8          (i:8, f:8)
@0x001b BSRL v4, v2, v6
@0x001d iTOb v4
@0x001e CpyVtoV4 v5, v4
@0x0020 SUSPEND 
@0x0021 SetV4 v6, 0xff          (i:255, f:255)
@0x0023 BAND v4, v0, v6
@0x0025 iTOb v4
@0x0026 CpyVtoV4 v7, v4
@0x0028 SUSPEND 
@0x0029 CpyVtoV4 v6, v3
@0x002b ubTOi v6
@0x002c CpyVtoV4 v4, v7
@0x002e ubTOi v4
@0x002f BOR v65535, v6, v4
@0x0031 SUSPEND 
@0x0032 SetV4 v4, 0x8          (i:8, f:8)
@0x0034 BSLL v2, v65535, v4
@0x0036 CpyVtoV4 v6, v1
@0x0038 ubTOi v6
@0x0039 BOR v4, v2, v6
@0x003b CpyVtoV4 v8, v7
@0x003d ubTOi v8
@0x003e BOR v65535, v4, v8
@0x0040 SUSPEND 
@0x0041 SetV4 v8, 0x8          (i:8, f:8)
@0x0043 BSLL v6, v65535, v8
@0x0045 CpyVtoV4 v2, v1
@0x0047 ubTOi v2
@0x0048 BOR v8, v6, v2
@0x004a CpyVtoV4 v4, v3
@0x004c ubTOi v4
@0x004d BOR v65535, v8, v4
@0x004f SUSPEND 
@0x0050 SetV4 v4, 0x8          (i:8, f:8)
@0x0052 BSLL v2, v65535, v4
@0x0054 CpyVtoV4 v4, v1
@0x0056 ubTOi v4
@0x0057 CpyVtoV4 v8, v3
@0x0059 ubTOi v8
@0x005a BXOR v6, v4, v8
@0x005c BOR v65535, v2, v6
@0x005e SUSPEND 
@0x005f CpyVtoR4 v65535
@0x0060 RET 2

本文仅供学习与交流,不得用于非法目的。如有侵权,请联系作者!