南X周易系列逆向思路-就改一行代码
本帖最后由 YMXuan 于 2020-6-21 00:33 编辑## 更新
填坑|南X周易程序超出试用的解决办法
https://www.52pojie.cn/thread-1204354-1-1.html
(出处: 吾爱破解论坛)
## 1.缘起
前几天在咱们论坛发了一篇(https://www.52pojie.cn/thread-1189302-1-1.html),反响还不错。然后正好找到了一款竞品,就是下面这位了。其实这篇文章从发完X奥就一直在酝酿了,但是技术不过关,一直没达到完美的欺骗效果。今天顺藤摸瓜就摸到了一个关键点,改一下就好。如果不想看思路的就直接看第6部分,如果想看思路的就往下翻翻,看完全文,不过思路挺长的,但是如果您像我一样,也是小白,看完还是有收获的。
## 2.软件介绍
八字、六爻、风水、奇门、姓名、择吉、合婚,多种平台上应用,不断完善!
## 3.软件列表(版本在支持系统里面有区分)
**练手包下载地址在附件里**
以下安卓手机软件都可免费试用**30**次。
> 当然,跟着我做完以后就没有使用限制了
1. 批八字算命
2. 玄空风水
3. 综合排盘
4. 六爻断卦
5. 奇门适甲
6. 专业起名
7. 八字合婚
8. 择吉程序
9. 金口诀
10. 八字用神
11. 南方万年历
12. 家居风水
13. 六爻排盘
14. 八字排盘
## 4.支持系统
**V1.73**,适用于安卓系统4.0版至安卓7.11版。
**V1.81**,适用于安卓8.0及以上版本。
## 5.逆向程序
MT管理器
## 6.害,我都不好意思写标题
方法很简单,MT进入安装包,DEX++编辑器打开classes.dex文件,打开An_xxxxxActivity,搜索`add`,第一个结果如下:
```
add-int/lit8 v0,v0,0x1
```
删除这一行,一路保存后编译、签名。
好了,破解完了。
**说句题外话,兄弟们,看着玩意没用,这句add-int/lit8 v0,v0,0x1是我找出来的,不是你。就像我能按照小夜大佬交代的查找ChkNum来欺骗X奥系列软件一样。换成这个系列我又啥都不会了。所以你只看这句价值不大,想学点东西还得看下面的思路。**
**至于成品嘛,这么简单你还好意思要成品?**
## 7逆向过程
以“八字排盘 V1.73”为例,首先安装原版软件点完试用的30次,从第31次开始,则无法正常排盘,会引导注册。如图
![](https://gitee.com/obiu/obiu/raw/master/img/20200529192919.png)
我们点击注册,然后会弹出注册页面
![](https://gitee.com/obiu/obiu/raw/master/img/20200529193042.png)
然后有以下几种情景:
- 点击“确定”,提示“您还没有输入注册码”
- 随便输入注册码,点击“确定”,提示“您输入的注册码不对”
- 点击“退出”,则返回软件主页面
好了,直接进到MT管理器里,打开apk文件,使用DEX编辑器++打开classes.dex
随便浏览一下类列表,有一个“activity_register”类。
然后点击搜索,搜索上面几个关键词。
比如搜索“注册”可以看到以下结果
!(https://gitee.com/obiu/obiu/raw/master/img/20200529193634.png)
我们点击“您已经注册了本程序”这一条,看到如下代码:
```
if-nez v1, :cond_96
sget-boolean v1, Lcom/nfbazi/PaiBazi/a/a;->f:Z
if-eqz v1, :cond_aa
:cond_96
const v1, -0xffff01
invoke-virtual {v0, v1}, Landroid/widget/TextView;->setTextColor(I)V
const-string v1, "您已经注册了本程序。"
invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V
const-string v0, "************"
invoke-virtual {v6, v0}, Landroid/widget/EditText;->setText(Ljava/lang/CharSequence;)V
const/4 v0, 0x0
invoke-virtual {v6, v0}, Landroid/widget/EditText;->setEnabled(Z)V
:cond_aa
return-void
.end method
```
这里面有两条判断:
```
if-nez v1, :cond_96
if-eqz v1, :cond_aa
```
关于Smali语法的解读,我们可以[参考这篇文章](https://blog.csdn.net/Ceryool/java/article/details/51314394):
> if-eq 如果等于
> if-ne 如果不等于
> if-lt 如果小于
> if-le 如果小于等于
> if-gt 如果大于
> if-ge 如果大于等于
> if-eqz 如果等于零
> if-nez 如果不等于0
> if-ltz 如果小于零
> if-lez 如果小于等于零
> if-gtz 如果大于零
> if-gez 如果大于等于零
> ————————————————
> 版权声明:本文为CSDN博主「Ceryool」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
> 原文链接:https://blog.csdn.net/Ceryool/java/article/details/51314394
我们现在知道了
if-nez v1, :cond_96 #如果v1不等于0,就执行cond_96的代码
if-eqz v1, :cond_aa #如果v1等于0,就执行cond_aa的代码
我们通过第一个代码块可以发现cond_96的代码就是包含"您已经注册了本程序。"的那一堆,cond_aa返回空,就是什么都不返回。
可以推断一下,未注册的情况下,v1应该是0,注册后,v1就不是0了,此时返回cond_96,也就是"您已经注册了本程序。"
那我们就欺骗一下,让未注册情况下,也能返回"您已经注册了本程序。"有两种途径可以实现此目的
- 修改if-nez v1, :cond_96为if-eqz v1, :cond_96
- 修改if-eqz v1, :cond_aa为if-eqz v1, :cond_96
修改之后,我们可以看到效果如图
!(https://gitee.com/obiu/obiu/raw/master/img/20200529194936.png)
但是经过测试之后我发现,这只是个美化效果,也就是说,我们只是更改了外观,并没有欺骗软件以达到注册的目的
好,接下来接着操作。
回到MT管理器,先反编译一下AndroidManifest.xml,这里是软件权限,我感觉给它联网权限不太好,那么删除`android.permission.INTERNET`就行了
```xml
......
<!-- 拥有完全的网络访问权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 读取手机状态和身份 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- 修改或删除您的USB存储设备中的内容 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<!-- 检索正在运行的应用 -->
<uses-permission android:name="android.permission.GET_TASKS" />
<!-- 安装快捷方式 -->
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<!-- 此应用可显示在其他应用上方 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
```
下面是软件的Activity
```xml
<!-- 这块注释是我加的,一般第一个Activity就是程序的入口(主界面),不过也不绝对 -->
<activity
android:theme="@style/startback"
android:label="@string/app_name"
android:name=".An_PaiBaziActivity"
android:configChanges="keyboardHidden|orientation"
android:windowSoftInputMode="adjustPan">
<intent-filter>
<!-- 但是有下面这句 android.intent.action.MAIN 就确定了,这就是入口(主界面) -->
<actionandroid:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 下面的代码被我精简了,省篇幅,主要就是让大家看一下有这么几个Activity -->
<activity android:name=".activity_register" /> <!-- 注册 -->
<activity android:name=".webshow"/> <!-- 排盘结果 -->
<activity android:name=".baziList" />
<activity android:name=".BaziToShengri" />
<activity android:name=".BaziSetting" />
<activity android:name=".SelectSet" />
<activity android:name=".DushuList" />
<activity android:name=".textShareActivity"/>
<activity android:name=".ShareOrFeedback" />
<activity android:name=".BazippActivity" />
<activity android:name=".RuanjianXuyong" /> <!-- 软件续用 -->
<!-- 没写注释的Activity都是实现具体软件功能的,不是咱们的欺骗目标 -->
```
看完之后,接着使用DEX编辑器++打开classes.dex,刚才咱们修改了activity_register,并且发现这里面并不是验证是否激活的关键所在。
然后看一眼An_PaiBaziActivity,毕竟这是主界面,信息会多一些。Smali可读性比较差,我们转化成Java代码,可是代码有很多,看哪?咱们在一开始测试过,试用次数没了之后,再使用就会跳转到软件续用界面,那我们就搜一搜什么条件下跳转,搜索我们在AndroidManifest.xml看到的RuanjianXuyong,然后我们发现就一处可以找到:
```Java
public void b() {
Intent intent = new Intent();
intent.setClass(this, RuanjianXuyong.class);
startActivity(intent);
}
```
这句代码是什么意思呢。因为我没学过Java。所以我也没看明白,不过我清楚,肯定就是从这里跳转到软件续用界面去了。
我们不想见到软件续用界面,因此就不能让它`intent.setClass(this, RuanjianXuyong.class);`
所以,我就兴致勃勃的在Smali代码中定位RuanjianXuyong
```
.method public b()V
.registers 3
new-instance v0, Landroid/content/Intent;
invoke-direct {v0}, Landroid/content/Intent;-><init>()V
const-class v1, Lcom/nfbazi/PaiBazi/RuanjianXuyong;
invoke-virtual {v0, p0, v1}, Landroid/content/Intent;->setClass(Landroid/content/Context;Ljava/lang/Class;)Landroid/content/Intent;
invoke-virtual {p0, v0}, Lcom/nfbazi/PaiBazi/An_PaiBaziActivity;->startActivity(Landroid/content/Intent;)V
return-void
.end method
```
用我这脑子想,就是,我不想要哪一行就直接注释掉哪一行,于是我在 `const-class v1, Lcom/nfbazi/PaiBazi/RuanjianXuyong;`前面加了一个#,然后心满意足的转进Java看了一眼,注释错了,然后我返回Smali代码,又把 `invoke-virtual {v0, p0, v1}, Landroid/content/Intent;->setClass(Landroid/content/Context;Ljava/lang/Class;)Landroid/content/Intent;`注释掉了,结果还是不行。
我不禁陷入了深思!我为啥要注释掉它?因为我不想见到软件续用啊;那我想见到什么?肯定是排盘结果啊!那我直接把RuanjianXuyong替换成webshow不就行了吗?!我可真是个小机灵鬼,说干就干。
然后我就把这行代码改成了:
```
const-class v1, Lcom/nfbazi/PaiBazi/webshow;
```
一路保存,反编译,覆盖安装,排个八字
!(https://gitee.com/obiu/obiu/raw/master/img/20200603140133.png)
嘿嘿,剧本好像有点不一样,软件试用界面是没了,排盘界面也出来了,可是内容去哪了?玩呢?气得我脑瓜子嗡嗡的,算了,先不破了,看看这软件哪里还有验证的地方。。咋这么麻烦呢?
这还有一个显示命例,按钮,点进去一看就是个档案夹的功能,可以时不时的把保存的案例拿出来复盘。然后我随便点了一个,想要看看复原效果,结果又看到这个熟悉的面孔了:
!(https://gitee.com/obiu/obiu/raw/master/img/20200603141101.gif)
事情进行到这里,我又要开始深思了
- 这软件到底搞了多少个验证的地方?
- 验证方法是啥?
- 就我这水平还能不能破完给我可爱的坛友写文章去了?
- 这软件就没啥总阀门吗?
嗯~总的,我好像嗅到成功的味道了,突破点在试用次数上!试用期间功能不受限制,说干就干。成功我来了。
痛定思痛、细心研读An_PaiBaziActivity的Java代码(我能看懂?其实我就是看看我认识哪几个单词)
147行:如果我没猜错的话,trytimes就是试用次数吧,我真是个小机灵鬼
```Java
private void e() {
this.h = getSharedPreferences("trytimesxml", 0);
a.k = Integer.parseInt(this.a.b(this.h.getString("tms", this.a.a("65AB24201"))).substring(7, 9));
if (a.k < 32) {
a.k++;
}
this.h.edit().putString("tms", this.a.a("65AB242" + String.valueOf(h.e(a.k)))).commit();
a.d("trytimesxml");
a.a("trytimesxml.xml", "system.out");
}
```
看完之后,啊,这段代码就是。。嗯,,那啥的、、是吧。(球都没看懂)
不过,这段代码过于眼熟
```java
if (a.k < 32) { /*如果a.k < 32,就执行a.k++*/
a.k++; /*a.k++ 就是 a.k = a.k + 1*/
} /*a.k = a.k + 1就是说a.k每次加1*/
```
我混迹吾爱数载,一看这货就是在计数,没猜错的话,应该是这样的:
```java
if(使用次数 < 32) {
使用次数++;
}
/*“使用次数”在具体程序中会对应不一样的变量名,表示的都是使用次数的意思*/
```
然后我又开始我的脑洞了:
- 把**试用**次数设置为一个特别大的数,然后就随便试用呗!
- 不行,再多的**试用**次数也有用完的时候?
- 那就让**使用**次数自减,这样永远都小于32
- 不行,减成负数报错怎么办?
- 即便不报错,减到特别小,程序会不会崩也是个问题
- 为什么不锁定**使用**次数?
啊哈,直接把`使用次数++;`删掉不就行了,我真是个小机灵鬼。当然上面是java代码,咱不能修改,那咱就去改Smali代码,怎么定位呢?反正我看不懂这东西,翻也不见得能翻到,先去网上冲一会儿浪:
> ### ARM常用指令
>
> **ADD** 加指令
>
> **SUB** 减指令
>
> **STR** 把寄存器内容存到栈上
>
> **LDR** 把栈上内容载入一个寄存器中
>
> **.W** 是一个可选指令宽度说明符。不会影响为此指令的行为,它只是确保生成32位指令。
>
> **BL** 执行函数调用,并把使lr指向调用者的下一条指令,即函数的返回地址
>
> **BLX** 同上,但是在ARM和thumb指令集间切换
>
> **CMP** 指令进行比较两个操作数的大小
>
> 来源: wOw的博客
> 文章作者: Wossoneri
> 文章链接: Decompile-smali/#toc-heading-23](http://wossoneri.github.io/2019/09/12/Decompile-smali/#toc-heading-23)
> 本文章著作权归作者所有,任何形式的转载都请注明出处。
浪了一圈,哦不,搜索了一圈,我感觉我又行了,看样子,直接搜add就行,然后这是搜索add的第一个结果:
```
1.const/16 v1,0x20
2.if-ge vo,v1,:cond_38
3.sget vo,Lcom/nfbazi/LiuyaoPaipan/a/a;>l:I
4.add-int/lit8 v0,v0,0x1
5.sput vo,Lcom/nfbazi/LiuyaoPaipan/a/a;>l:I
```
第1行就是给v1赋值,16位的20正好对应十进制的32。十六进制20=2\*16<sup>1</sup>+0\*16<sup>0</sup>=32
第2行就是那个判断语句
第4行就是让使用次数+1
第3、5行兴许是传递参数的?看不懂
我们只需要删除或者注释掉`add-int/lit8 v0,v0,0x1`即可,注释就是在行首输入#。删除就是直接删掉。
然后保存、编译、签名、安装、测试。畅通无阻。
## 软件截图
略,分析里面都有了,截图没意义。
## 总结
### 感悟
有个故事:
> 一天,一个农夫的农机坏了。自己怎么修也修不好。没办法,只能找人来修了。他请来一个修机器的,那人左看看又看看,最后在机器的某个部位,敲了一下,然后机器重新启动起来。农夫很高兴,于是便问:“多少钱。”那人说:“200美元”,农夫说:“这么贵”。那人说:“这一锤子并不贵,但是,这一锤子往哪敲就贵了”。
其实小夜大神的搜索ChkNum和我的搜索add都没啥价值,分析思路是我得到的教训,这是有价值的地方。所以希望想自己学逆向的同学可以看看我的思路,少走一些弯路,而不是学会搜add,从我的例子来说,学会搜ChkNum对于此次欺骗毫无价值。
### 其他小问题
- 如果在删除`使用次数++`以前就用完了32次试用机会,触发了跳转到软件续用界面,再覆盖安装欺骗版的,仍然会跳转到软件续用界面。解决方案:https://www.52pojie.cn/thread-1204354-1-1.html
- 如果把时间调到几年后(具体没测试),会弹窗提示软件过期、悬浮窗提示软件过期,但是没什么影响,跳过即可。去除弹窗、悬浮窗可以在论坛搜一些其他大佬的帖子,或者我晚一些时间再补一个帖子。
- 如果仅删除add那一句,注册界面仍然显示未注册,这个地方就是个强迫症问题,想改就改,不想改的话正常使用也没人点进去看。
- 这几个小问题我还没着手解决,如果你尝试解决时遇到难题,可以在评论区交流,或者在论坛求助。
---
**这个帖子前后耗时6个多小时吧,总算写清楚自己思路了,希望对您有用,也期待大家踊跃互动,我目前着手欺骗一个新的系列,这一系列的软件颜值很高,像是iOS界面,如果这贴子反响比较好,我就抓紧把这个高颜值的欺骗手段放出来。**
最后这句话没什么用,就是怕复制MD出错。 kenxy 发表于 2020-6-5 10:09
我下载了一个批八字 an_Pibazi80.apk安装包,用MT打开后,我选择了Classex.dex,然后用dex编辑器++打开, ...
感谢你的支持,你能说出自己的思路,肯定认真看了我的思路。其中,在反编译xml的时候我贴了一段代码
<!-- 这块注释是我加的,一般第一个Activity就是程序的入口(主界面),不过也不绝对 -->
<activity
android:theme="@style/startback"
android:label="@string/app_name"
android:name=".An_PaiBaziActivity"
android:configChanges="keyboardHidden|orientation"
android:windowSoftInputMode="adjustPan">
<intent-filter>
<!-- 但是有下面这句 android.intent.action.MAIN 就确定了,这就是入口(主界面) -->
<actionandroid:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
这里面写到了寻找主界面Activity的方法,由于我直接在代码里注释的,所以可能很容易忽略掉,下次我写到正文中。
然后寻找An_xxxxActivity是173版本的办法,后来我试了181版本的,确实不太一样,但是我们触类旁通,可以反编译你说软件的xml,可以看到
<activity
android:name="com.nfbazi.pibazi.splash_activity">
<intent-filter>
<action
android:name="android.intent.action.MAIN" />
<category
android:name="android.intent.category.LAUNCHER" />
<action
android:name="android.intent.action.VIEW" />
</intent-filter>
</activity>
<activity
android:name="com.nfbazi.pibazi.MainActivity" />
然后用DEX++打开dex文件,发现com.nfsoft.pibazi中的splash_activity并不是我们要找的,再去看MainActivity,发现是我们要找的,我们修改这个就可以了。
这里还有一个问题,那就是,181版本的,搜add的话,可能第一个结果不是我们要改的,具体位置不确定,你可以先搜搜看,如果有困难了,我们再交流。
感谢支持 東君丶 发表于 2020-6-4 18:31
我是小白,但是我实际操作了一下,直接搜索删除,保存,然后点一下小锤子编译,退出,最后安装,发现还是老 ...
你说的老样子是什么样?
如果在删除使用次数++以前就用完了32次试用机会,触发了跳转到软件续用界面,再覆盖安装欺骗版的,仍然会跳转到软件续用界面。这个问题现在还没解决
如果仅删除add那一句,注册界面仍然显示未注册,这个地方就是个强迫症问题,想改就改,不想改的话正常使用也没人点进去看。这并不影响使用
如果之前没试用结束,删了这一句之后可以先测试50遍左右,你就知道超过32次仍然可以正常使用 不错听说这个软件比较有名{:301_1000:} 本帖最后由 涛之雨 于 2020-6-3 16:09 编辑
害本来准备写注册机跑算法的。。。穷举法有点。。。
太慢了....(15位数字自增。。。。)
真的很详细啊,非常好的学习资料 涛之雨 发表于 2020-6-3 16:08
害本来准备写注册机跑算法的。。。穷举法有点。。。
太慢了....(15位数字自增。。。。)
啊,这是玄奥那个吧。
{:1_918:}你看看南X的算法简单些不?
我这种战五渣就不想了 谢谢分享 下下来就是一代大师了 Strugglion 发表于 2020-6-3 16:27
下下来就是一代大师了
:lol又省了大几千,官网一套下来不便宜呢 wushuang100 发表于 2020-6-3 16:07
不错听说这个软件比较有名
这家有年头了 感谢楼主的分享!!