关于付费 APP 加密方式的一些疑问

2022-07-01 08:56:34 +08:00
 equationl

可能因为我的水平有限,我所能想到的加密方式似乎都没有实际用处。

比如,某个业务场景是付费用户可以创建无限的素材,而非付费用户只能创建不高于某数量的素材。

虽然我已经用了各种方式来加密这个“某数量”的值,不过不管前面写的多绕,但是最终还是会回归到一个 if 逻辑判断。

比如,针对上述场景,我是这样写的:

    val a = "RWM="
    val b = a.decodeBase64()?.string(Charset.forName("utf8")) ?: "${Char(0)}${Char(0)}"

    val realNum = 10

    if (realNum <= b.toCharArray()[1].code - b.toCharArray()[0].code) {
        println("pass")
    }
    else {
        println("you need pay first")
    }

但是反编译下来是这样的:

(节选)

      if (realNum <= var8 - var3) {
         var4 = "pass";
         System.out.println(var4);
      } else {
         var4 = "you need pay first";
         System.out.println(var4);
      }

不管你前面写的有多复杂,最终还是会回归到简单的 if 逻辑判断,别人修改时只需要删除或者直接改一下这个判断就能轻松破解了。

判断是否付费同理,不管前面写的多复杂,最终会回归到简单的逻辑判断。

所以我现在加密完全依赖于加固,但是加固也只能防的住“脚本小子”一键破解,如果是有大牛专门破解的话,也很容易被脱壳。

虽然防住“脚本小子”已经满足我的需求了,但是我还是想知道,应该怎么在不加固的情况下避免被“一键”破解呢?

据我所知,某个业内有名的 APP ,就没有使用加固,但是就是至今没被破解,甚至作者还专门搞了个悬赏,让别人区破解它。据我了解的是,至今没人破解成功。

补充:

  1. APP 性质决定了没法将需要加密的逻辑放到服务器避免被本地破解
  2. 上述代码只是做了一个示例,实际使用比这个复杂,而且核心代码也是写在 c++ 中的,不过正如我上述说的,即使写在 c++ 中,最终也是回归到 java 调用 c++的代码,然后做一个简单的逻辑判断
  3. 我想到的是把加密和业务逻辑混合在一起,但是自己实际测试过,即使这样反编译后效果也不理想。
6723 次点击
所在节点    程序员
67 条回复
XiLingHost
2022-07-01 22:56:08 +08:00
不要让程序崩溃,把你的授权包含在业务逻辑里,授权即使不正确依旧可以正常得到错误的结果,错误和正确的结果应当是无法简单区分的
InDom
2022-07-01 23:00:15 +08:00
说白了,这个 if 一定会存在,但能不能尽可能难的定位到(发现问题的时候,已经离执行这个 if 很远了。)。

先从别的地方获取到 你判断需要的数据准备好,但不使用。

另一个地方,在不同的时间线上,获取这个数据,不要判断,转化为特定的,与授权无关的几个值之一。

比如 is_main = "info" / debug / hello / index

。。。中间隔着非常多的代码

然后,['info' => fun_yes1, 'debug' => fun_no0, 'hello' => 'fun_no', 'index' => fun_yes5]('test_body', args1...)

这个地方,做了一次分流,如此多来几次,代码里面没有出现 if ,但其实在无形之中,做了很多次,判断。

同样的思路,可以用 for (i in 5|7|1|3|2|6|4 ){switch {case 2: code1;case 5: code2,i++ ;... } }

这个 fun 里面 已经在执行 正确的 或者错误的功能代码了。

跟踪代码的时候,基本不会关注这里,因为这里的代码 执行看起来都很正常。

然后到了某个地方,突然发现,这个地方没有 if 。就代码里面硬写死了 中断, 那就要回去找,是什么地方 引导流程到这里来的。

回头去找代码执行来源,就很头疼了,因为不知道从什么地方开始的,只能从头一点点跟踪...

如果想找到合心的代码,就必须从头到位,一直不拉的全部梳理一遍,才能找到核心。
gen900
2022-07-02 06:35:38 +08:00
我以前试过用抛异常代替 if 判断。比如 运行时间 /a ,合法用户 a=0 ,然后在合适位置 catch 这个异常走正常流程。没有逆向过不清楚这样是不是也回归到 if 判断
plumk
2022-07-02 15:53:57 +08:00
核心代码写在动态库,生成 md5 ,在运行的时候某个不起眼的地方检查动态库 md5 ?
dousha99
2022-07-03 13:29:33 +08:00
可以参考 Rare 的做法:写很多相互校验的逻辑,然后加壳混淆再加密,最后留一个一眼就能看出来的 `if`. 这个一眼就能看出来的 `if` 实际上是一个诱饵,破解之后可以被后面隐藏的校验逻辑所感知。当然做戏要做全套,验证出这个诱饵被破解之后,先不要着急弹没有授权的框框,而是让程序先能用一段时间;之后就开始莫名奇妙地崩溃、卡死、删存档。表面上是破解了,实际上完全没有破解,而且用户体验会特别特别差。尤其是生产力工具,卡死带删档基本上是绝杀。

大部分脚本小子和经验不足的破解者可能在干掉这个诱饵之后就打包发布了,殊不知发布的版本是完全没法用的。虽然说最终那些隐藏的相互校验的逻辑也会被大神破解掉,但是这个正版验证的目的已经达到了:让程序尽可能晚被破解。
HiLyy
2022-07-05 04:39:55 +08:00
可以这么写:
user_input_code = 123
real_code = 31456
key = abs ( real_code - user_input_code ) # 如果用户输入的注册码正确,key 为 0 ,否则为一正整数
......
# 在生成素材的时候
for i in range ( 0 ... key ):
# 在素材中添加随机噪音

除非用户能完全阅读代码,否则应该没人会想到要给 key 赋 0 吧。如果再绕一点,可以这样:
user_input_code = 123
real_code = 31456
key = real_code - user_input_code # 如果用户输入的注册码正确,key 为 100 ,否则为其它数
......
# 在生成素材的时候
for i in range ( 0 ... abs ( key - 100 ) ):
# 在素材中添加随机噪音

在这种情况下,应该很难破解了吧?
HiLyy
2022-07-05 04:42:50 +08:00
不知道为什么上面的缩进给没了,再试一下
可以这么写:

user_input_code = 123
real_code = 31456
key = abs ( real_code - user_input_code ) # 如果用户输入的注册码正确,key 为 0 ,否则为一正整数
......
# 在生成素材的时候
for i in range ( 0 ... key ):
# 在素材中添加随机噪音

除非用户能完全阅读代码,否则应该没人会想到要给 key 赋 0 吧。如果再绕一点,可以这样:

user_input_code = 123
real_code = 31456
key = real_code - user_input_code # 如果用户输入的注册码正确,key 为 100 ,否则为其它数
......
# 在生成素材的时候
for i in range ( 0 ... abs ( key - 100 ) ):
# 在素材中添加随机噪音

在这种情况下,应该很难破解了吧?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/863351

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX