Android版权保护的困难及迷惑

grayhat2005 2016-01-14 03:14:22
我是新手,可能很多问题别人早就想过了,我说出来有些好笑,不过还是说一说,希望有高手指点。
我在写Android程序时低估了版权保护的困难性,辛辛苦苦写了一年,现在面临发布了,查so库的保护才发现几乎是不可能的。
以前认为什么东西进了C++就安全了,虽然微软的东西也可以被破解,但我这种小东西谁犯得上花那工夫去弄呢。我一直有这么一个比喻:马路边停着各式各样的车,有宝马,有奔驰……要是有人偷车,难道会偷我的破吉利吗?费那么大劲,当然要选择高回报了。但是仔细琢磨,软件世界的逻辑不太一样:
一、好“车”烂“车”都有人偷。
二、他不怕费劲,甚至就是为了享受这个过程而去费这个劲的。
三、而且可能并不费多少劲,现在有IDA等神器操作起来实在是太方便了,找到关键跳转可能只是半小时的事。
那就是说我辛辛苦苦干了一年的东西,人家半小时就盗了。不是因为这东西好,或者能卖多少钱,只是随机分配到了某个反汇编爱好者手里,他只是拿我这个来练手,解开了也不卖,往网上一丢,“×××破解版”出现了,他再找别的课题来享受。
这就和偷车很不一样,偷软件可能是非营利性的,非理性的。
回头说技术细节。so加壳有很多资料,解壳的资料也同样多,我并不确定根据网上公开的资料加的壳好不好使,而更高级的加壳技术,像爱加密的定制,报价几万到几十万,我这小软件不值得花这钱,又没有把握在缺乏资料的情况下独立写出来,那加壳基本上就不靠谱了。
其实我确实加了壳,在这里,有一个很好的教程:http://blog.csdn.net/jiangwei0910410003/article/details/49962173。但我用NDKr10生成的so文件无法自解密,那篇文章的高手用的是较低版本的NDK,如果我要用这个版本,就要把AndroidStudio换成Eclipse,整个项目都要调整,而就算加壳成功还不知道是否实用,我相信网上一定有成千上万的人照着那篇文章加壳,也有无数反汇编爱好者研究相应的解壳方案。那就算了。
加壳再也没有更好的材料了,因此我放弃加壳。
这样就只好寄希望于代码本身的抵抗力。
绝对强的抵抗力是没有的,连铁道部官网的加密算法都一再被破,何况我们的。
我们的诉求应该表达为:给反汇编者造成足够的困难,超出破解的价值或乐趣。
...全文
209 3 打赏 收藏 举报
写回复
用AI写文章
3 条回复
切换为时间正序
请发表友善的回复…
发表回复
grayhat2005 2016-01-14
  • 打赏
  • 举报
回复
我想破解者拿到这样的程序会怎么办。 他首先要找注册的按钮。找到了,随便填注册码,如果有报错它会搜报错字符串找源头,但是没有,他得到的是“感谢您注册”这样中庸的语句,注册成功与否都一样,没有验证,没有跳转,只有注册码存档。 “那么验证一定是在so里。”他会这样想。挂起IDA,打开so,他开始找硬件识别语句,很快就找到了,因为相应函数名是固定的。这个函数输出了一个值,经过复杂的计算又输出A、B、C、F、H、I、L、O、P、Q,这都不是问题,IDA太强大了,顺藤摸瓜都能找到。 然后他查哪些函数调用了A、B、C、F、H、I、L、O、P、Q,发现很多。傻子才一一排查呢,他会从另一头下手:注册码。 注册码要么是用户输入的,要么是读某个文件得到的。若是用户输入,java端会有个输入框,若是读文件,也有读文件的函数,这都隐藏不了。找到以后看它流到了哪儿,哦,原来是进了so库,还分成了D、E、G、J、K、M、N、R、S、T。 到此为止,破解者对作者的手法也有了一定的了解——原来他喜欢摆稻草人。这有助于他下面的破解。 刚才找不着硬件信息也没关系(软件作者有可能把电话号码弄成机器码,或处心积虑想到的另类信息),破解者暂时忽略这一头,而专攻注册码那一头。注册码的来源无法隐藏,找到以后,看哪些变量与它比较,最终顺藤摸瓜还是能找到机器码。 他最终要找的,是比较机器码和注册码的函数。 如果这个函数是单一的,“if (dc.Equals(sn)) return true; else return false;”,他一下就找到了,而且会嘲笑作者。但我们有20个变量代表注册码和机器码,比较它们又用到若干个函数。 设想破解者面临的稍微困难的局面——找不到机器码,先找到了注册码。注册码没有和任何东西直接比较,而是分成了D、E、G、J、K、M、N、R、S、T。然后他搜哪些函数调用了这些变量,哪些变量与这些变量发生关系,那些变量又是从哪儿来的,他迟早会发现一个可疑的读信息的函数,于是他知道机器码是什么了,也就列出了A、B、C、F、H、I、L、O、P、Q。 接着他回到D、E、G、J、K、M、N、R、S、T与A、B、C、F、H、I、L、O、P、Q发生关系的若干函数中,看它们发生关系的性质。不是简单地输出true或false,而是生出一窝新的变量,有用的变量就藏在其中。 软件作者知道,有用的、决定注册成功与否的变量,是a、b、c、d、e……,但破解者不知道啊,到此为止,他快疯了。 如果他跟这个软件不共戴天,可以熬几个通宵,追踪A、B、C、F、H、I、L、O、P、Q与A、B、C、F、H、I、L、O、P、Q下的所有的崽,相信他能找到出口。 但是且慢,他可以采用另一策略。 他急了,跟软件作者拼了,于是他花钱买了这个软件。 于是他有了合法的注册码和公钥,跟自己的机器码对应。 这个公钥对他来说是形同虚设的,因为他完全可以查到解密后的注册码。 他把解密后的注册码、自己的机器码,写入so库,这样一来无论中间的过程有多复杂,结果都是正版的了。 他把这个软件发到网上,另一个人下载了。 到了这个人的手机上,硬件信息是不一样了,但由于识别硬件的指令已经被篡改,无论硬件是什么,识别的结果都是破解者的硬件号,和破解者存在so库里的注册码匹配,于是打开就是正版的,都不需要填写注册码。
grayhat2005 2016-01-14
  • 打赏
  • 举报
回复
所以这种注册模式玩再多的花样也没用,不管你把加密解密搞得多复杂,最后总有两样东西是藏不起来的:读硬件、比较字符串。哪怕你中间有一万行复杂的语句,但破解者只需要知道两头的东西就够了。 入口:读硬件 中间XXXXXXXXXX也许很多 XXXXXXXXXXXXXXXX XXXXXXXX 出口:比较字符串 只要校验过程放在客户端,这就是个硬伤。 我也想过迷惑入口或出口的办法。比如入口突然中断,读完硬件给一堆变量赋值就撒手不管了,然后java端动不动向so库发出一些呼叫,有用的没用的都掺和在一起,看起来仿佛是在计算控件属性、运行变量等,其实这些都没必要到so库里去算,它们真实的意图是把刚才读机器码弄出的那堆变量组织起来。对于注册码也经过同样的分散处理。到关键时刻某个函数把机器码和注册码拿来比较,也许是神不知鬼不觉的吧。我以前是这么想的,如果反汇编看不到函数实现的细节,它看到的就只是一堆变量莫名其妙地变来变去,被多种函数反复调用,弄不清哪次调用是关乎注册的。但IDA实在是太强大了,每一次计算,每一个来源和去向,它都跟踪得一清二楚。 以下面的例子来考虑一下: 读机器码,赋值变量A、B、C。 干别的事。 读注册码,赋值变量D、E。 干别的事。 干别的事…… 读机器码…… 干别的事…… 读注册码…… 干别的事…… 和机器码有关的变量A、B、C、F、H、I、L、O、P、Q,经过函数F1的计算,会整合成机器码的值。 时不时有个呼叫从java传在so库里来,修改这些变量,但保持它们的F1结果不变。 这个呼叫并不单纯地修改上述变量,相反地,它看起来主要是干别的事,比如修改控件坐标、算积分之类的,它甚至调用与机器码有关的变量来干这些事。 注册码变量D、E、G、J、K、M、N、R、S、T也是这样。 在这多次呼叫中,有些呼叫同时调用了机器码变量与注册码变量。 其中又有一些,比较了机器码变量与注册码变量,并赋值了另一些变量来表示注册是否成功。这并不是傻乎乎的true、false,而是一些看起来没有意义的数字,它们的名称也是没有特点的,比如a、b、c、d、e…… 又有一些呼叫在改变这些数字,但保持它们整合的结果不变。 与注册有关的函数也调用了这些数字,在众多调用它们的函数中并不起眼。 这个函数不会输出“if (thisCode.Equals(legalCode)) return "恭喜你,注册成功!"; else return "请登录www.xxx.com付费";”这样的傻语句,它要做的是这样的事: 把a、b、c、d、e……灌到程序运行模块中去,计算关键的变量X、Y、Z…… 如果a、b、c、d、e……是合法组合(用正确的机器码和注册码计算出来的值),X、Y、Z……就是可以让程序正常运行的。 否则,X、Y、Z……就会让程序变态。 但是不退出,不崩溃,永远在跑,代码一切正常,调试也不报错,用户拿着手机,那个程序始终在喘气,只不过疯了。
grayhat2005 2016-01-14
  • 打赏
  • 举报
回复
典型的“一机一码”保护机制是:       公钥 ────────────────────┐ 【服务器】 私钥 ───> 注册码 ───┐    │          ︿          │    │          │           │    │          │           │    │         申请码         ﹀   ﹀ 【用户端】   机器码        注册码 公钥  (注册时)           │           │  │          ﹀           │  │          比较 <── 解密后 <────┘  (校验时)          / \          /  \        相同 不相同       注册成功 失败 相应的伪代码是: ///服务器端: byte[][] 注册程序(申请码, 机器码) { byte[] 公钥; byte[] 注册码; if (申请码验证通过) { Encoder 密钥生成器 = new Encoder(); byte[] 私钥 = 密钥生成器.私钥; //把私钥保存到数据库中,与用户信息对应 公钥 = 密钥生成器.公钥; //由于钥匙是随机产生的,因此每个客户的公钥不一样 注册码 = 加密函数(私钥, 机器码); } else { 公钥 = ""; 注册码 = "不存在这个用户"; } return new byte[] { 注册码, 公钥 }; } ///用户端: void 获得注册码 { byte[][] 反馈 = 服务器WebService.注册程序(申请码, 机器码); byte[] 注册码 = 反馈[0]; byte[] 公钥 = 反馈[1]; 记录(注册码, 公钥); } bool 校验 { byte[] 当前机器码 = 读设备信息(); byte[] 注册码 = 读注册码(); byte[] 公钥 = 读公钥(); byte[] 预期机器码 = 解密函数(公钥, 注册码); return 当前机器码 == 预期机器码; } 但是很简单就可以破解这个程序: 1.如果“校验”函数简单地返回true或false,找到它的出口一律“true”就是了。这个函数可能是包在so库里的,但还是能用IDA这样的神器打开,能改还能保存。现在改写动态链接库就像改txt一样容易,改完往apk包里一扔,重新签名完事。 2.如果出口不明显(比如分散成多个变量,让别的函数整合这些变量以决定是否正常运转),还可以从入口下手。入口的机器码是很难隐藏的,因为读设备肯定要用到公开的、无法将名称混淆的函数(像TelephonyManager这样),IDA一搜文本就找到了,再找它的返回值,再找代码中是什么东西在和它比较,然后,如果比较的语句很简单,就把比较结果改成true,如果作者玩花样,比如这儿比较一个字符,那儿比较一个字符,输出多个变量让以后的函数来整合,那破解者也不用费那么多心,直接把比较的源头改成两个一样的东西就行,比如注册码改成0,机器码也改成0。
代码下载链接: https://pan.quark.cn/s/a4b39357ea24 用户账户控制(UAC)白名单的配置 Windows7环境中 UAC(User Account Control,用户帐户控制)是由微软在Windows Vista版本中推出的一项旨在增强系统安全性的创新技术,该技术强制要求用户在执行可能干扰计算机正常运作的操作或进行更改会波及其他用户设置的变动前,必须提供相应的权限或管理员密码进行验证。通过对这些操作启动前进行授权确认,UAC能够有效阻止恶意软件及间谍软件在未获授权的状态下于计算机内进行安装或实施修改。 自从Vista版本问世以来,微软便开始推行这一全新的安全机制,可视为对系统安全防护的显著提升。尽管UAC确实能够在一定程度上对某些非法程序起到防御作用,但与此同时,这一功能也给众多用户带来了诸多不便。 因此,许多用户开始探寻是否存在类似于白名单的功能,以便将那些值得信赖的程序直接赋予运行权限。事实上,这类功能确实存在,不过微软并未将其作为标准配置提供。 网络上关于此问题的绝大多数建议都是建议禁用UAC,这种说法显然缺乏针对性,因为若用户希望禁用此功能,本就不会提出相关疑问。 通过运用微软官方发布的Microsoft Application Compatibility Toolkit 5.6版本,可以将信任的程序纳入系统白名单范畴。 获取Application Compatibility Toolkit 安装程序成功后会出现三个可执行文件 以管理员身份启动Compatibility Administrator 在Custom DataBases部分创建新的数据库,并添加一个Application Fix(在下方空白处点击右键,选择...

80,490

社区成员

发帖
与我相关
我的任务
社区描述
移动平台 Android
androidandroid-studioandroidx 技术论坛(原bbs)
社区管理员
  • Android
  • yechaoa
  • 失落夏天
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧