工作经验补充
安卓逆向经验
项目1-大鹅(共存)
用的MT,操作过程如下:
>一键共存,修改包名
>去除签名校验
>去除资源混淆
>查看进入,找到资源文件以翻译模式替换掉app_name的名称
>查看进入,找到icon.png,以新的图片替换掉原始图片,名称和文件格式保持一致
>再一次去除资源混淆(不再来一下发现在mt管理器中显示的apk图标还未更新,再来一下就更新了)
分身共存、改名和改图标后的截图:

项目2-手动还原smali成java代码
smali内容:
.class public Lnet/bluelotus/tomorrow/easyandroid/Crackme; .super Ljava/lang/Object; .source "Crackme.java" # instance fields .field private str2:Ljava/lang/String; # direct methods .method public constructor <init>()V .locals 1 .prologue .line 22 invoke-direct {p0}, Ljava/lang/Object;-><init>()V .line 21 const-string v0, "cGhyYWNrICBjdGYgMjAxNg==" iput-object v0, p0, Lnet/bluelotus/tomorrow/easyandroid/Crackme;->str2:Ljava/lang/String; .line 23 const-string v0, "sSNnx1UKbYrA1+MOrdtDTA==" invoke-direct {p0, v0}, Lnet/bluelotus/tomorrow/easyandroid/Crackme;->GetFlag(Ljava/lang/String;)Ljava/lang/String; .line 24 return-void .end method .method private GetFlag(Ljava/lang/String;)Ljava/lang/String; .locals 4 .param p1, "str" # Ljava/lang/String; .prologue const/4 v3, 0x0 .line 27 invoke-virtual {p1}, Ljava/lang/String;->getBytes()[B move-result-object v2 invoke-static {v2, v3}, Landroid/util/Base64;->decode([BI)[B move-result-object v0 .line 29 .local v0, "content":[B new-instance v1, Ljava/lang/String; iget-object v2, p0, Lnet/bluelotus/tomorrow/easyandroid/Crackme;->str2:Ljava/lang/String; invoke-virtual {v2}, Ljava/lang/String;->getBytes()[B move-result-object v2 invoke-static {v2, v3}, Landroid/util/Base64;->decode([BI)[B move-result-object v2 invoke-direct {v1, v2}, Ljava/lang/String;-><init>([B)V .line 30 .local v1, "kk":Ljava/lang/String; sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream; invoke-direct {p0, v0, v1}, Lnet/bluelotus/tomorrow/easyandroid/Crackme;->decrypt([BLjava/lang/String;)Ljava/lang/String; move-result-object v3 invoke-virtual {v2, v3}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V .line 31 const/4 v2, 0x0 return-object v2 .end method .method private decrypt([BLjava/lang/String;)Ljava/lang/String; .locals 8 .param p1, "content" # [B .param p2, "password" # Ljava/lang/String; .prologue .line 35 const/4 v4, 0x0 .line 37 .local v4, "m":Ljava/lang/String; :try_start_0 invoke-virtual {p2}, Ljava/lang/String;->getBytes()[B move-result-object v3 .line 38 .local v3, "keyStr":[B new-instance v2, Ljavax/crypto/spec/SecretKeySpec; const-string v7, "AES" invoke-direct {v2, v3, v7}, Ljavax/crypto/spec/SecretKeySpec;-><init>([BLjava/lang/String;)V .line 39 .local v2, "key":Ljavax/crypto/spec/SecretKeySpec; const-string v7, "AES/ECB/NoPadding" invoke-static {v7}, Ljavax/crypto/Cipher;->getInstance(Ljava/lang/String;)Ljavax/crypto/Cipher; move-result-object v0 .line 40 .local v0, "cipher":Ljavax/crypto/Cipher; const/4 v7, 0x2 invoke-virtual {v0, v7, v2}, Ljavax/crypto/Cipher;->init(ILjava/security/Key;)V .line 41 invoke-virtual {v0, p1}, Ljavax/crypto/Cipher;->doFinal([B)[B move-result-object v6 .line 42 .local v6, "result":[B new-instance v5, Ljava/lang/String; invoke-direct {v5, v6}, Ljava/lang/String;-><init>([B)V :try_end_0 .catch Ljava/security/NoSuchAlgorithmException; {:try_start_0 .. :try_end_0} :catch_1 .catch Ljavax/crypto/NoSuchPaddingException; {:try_start_0 .. :try_end_0} :catch_0 .catch Ljava/security/InvalidKeyException; {:try_start_0 .. :try_end_0} :catch_4 .catch Ljavax/crypto/IllegalBlockSizeException; {:try_start_0 .. :try_end_0} :catch_2 .catch Ljavax/crypto/BadPaddingException; {:try_start_0 .. :try_end_0} :catch_3 .end local v4 # "m":Ljava/lang/String; .local v5, "m":Ljava/lang/String; move-object v4, v5 .line 46 .end local v0 # "cipher":Ljavax/crypto/Cipher; .end local v2 # "key":Ljavax/crypto/spec/SecretKeySpec; .end local v3 # "keyStr":[B .end local v5 # "m":Ljava/lang/String; .end local v6 # "result":[B .restart local v4 # "m":Ljava/lang/String; :goto_0 return-object v4 .line 43 :catch_0 move-exception v1 .line 44 .local v1, "e":Ljava/security/GeneralSecurityException; :goto_1 invoke-virtual {v1}, Ljava/security/GeneralSecurityException;->printStackTrace()V goto :goto_0 .line 43 .end local v1 # "e":Ljava/security/GeneralSecurityException; :catch_1 move-exception v1 goto :goto_1 :catch_2 move-exception v1 goto :goto_1 :catch_3 move-exception v1 goto :goto_1 :catch_4 move-exception v1 goto :goto_1 .end method
手动还原结果:
package net.bluelotus.tomorrow.easyandroid; import android.util.Base64; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; public class Crackme { private String str2; public Crackme(){ this.str2 = "cGhyYWNrICBjdGYgMjAxNg=="; this.GetFlag("sSNnx1UKbYrA1+MOrdtDTA=="); } private String GetFlag(String str){ byte[] content = Base64.decode(str.getBytes(),0); String kk = new String(Base64.decode(this.str2.getBytes(),Base64.DEFAULT)); System.out.println(decrypt(content,kk)); return null; } private String decrypt(byte[] content,String password){ String m = null; try { byte[] keyStr = password.getBytes(); SecretKeySpec key = new SecretKeySpec(keyStr,"AES"); Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); cipher.init(Cipher.DECRYPT_MODE,key); byte[] result = cipher.doFinal(content); m = new String(result); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } return m; } }
项目3-白嫖某手游加速APP的VIP
运行时提示更新,先去掉更新。反编译后修改apktool.yml中的versionCode和versionName项


项目4-白嫖ESFileExplore的VIP
将com.estrongs.android.pop.app.premium.newui.b.onClick方法中的如下图中的2个条件变成true,即可完成整个破解

最终如下图:

项目5-破解单机斗地主支付
修改一个地方即可,com.june.game.doudizhu.activities.b.v.a方法

将图中的代码替换成new w(this).a(“伪造的支付响应结果”);
我伪造的响应结果:resultStatus={9000};memo={9000};result={200}
resultStatus必须是9000,其它的是我随意填的,不要问为什么,历经几小时分析出的。 new w(this).a(“伪造的支付响应结果”)的smali代码就不贴了

项目6-破解愤怒的小鸟实名认证和支付
去实名认证有2种方法:
其一:
com.xiaomi.gamecenter.sdk.web.VerifyIDWebFragment.onKey

注释掉它,这样实名认证的弹窗都不会出现的
其二:
com.xiaomi.gamecenter.sdk.web.VerifyIDWebFragment.codeDeal
将switch接收的值改成恒等于200即可,这样的方式实名认证弹窗会出现,但是允许关闭它,关闭后就不会再出现。

com.rovio.rcs.payment.talkweb.TalkwebPaymentProvider$1$2.onResponse
将switch接收的值改成恒等于9999即可

最终效果:

项目7-破解消灭星星支付
需要先去掉华为移动服务弹窗阻止
com.huawei.hms.common.internal.BaseHmsClient.connect

注释掉它即可
破解支付:
com.brianbaek.popstar.popStarA$3.handleMessage
1,去掉视频未准备的逻辑


我直接传入900个星币,效果图如下:

项目8-基于Xposed编写插件破解滑雪大冒险游戏
1,经过ddms分析点击购买事件,找到所在类为
com.yodo1.android.sdk.helper.Yodo1PayHelper$6$1$1
2,在jeb中分析发现最终购买成功与否的处理方法是该类里的onResult方法

3,测试破解发现直接调用switch方法底下的代码便可购买成功,如下图:

让逻辑直接走这儿,不经过switch方法便可成功购买。
结合上下文分析如果要想用xposed 进行hook不走switch方法,那么传入switch方法的参数得为假,但是errorCode得等于0xd0,即208 ,那么最终hook代码如下:

打包后安装到雷电模拟器,再把滑雪大冒险也安装好,安装这个xposed插件时会提示重启,我们重启之后,开始启动滑雪大冒险,点击购买,直接购买成功,如下图:

已买了一次,已经变成了20008 再点击购买,20008+20000=40008,看看是不是变成40008,如下图

项目9-基于Xposed编写插件破解赛车向前冲游戏
通过ddms发现点击购买时的类是cn.egame.terminal.paysdk.EgamePayJD$2$1,对应的方法是onClick,如下图

实际处理购买成功与否则是onClick方法里面的匿名内部类的方法onResult方法

通过xposed各种尝试发现需要先hook onClick方法,接着基于这个再hook onResult方法才能成功,hook代码如下:

打包安装,重启雷电,进入游戏,点击购买

点击购买提示购买失败,失败弹窗消退后,弹窗购买成功,如下图

项目10-过反调试解ctf
题目是阿里聚安全的一道题目
静态分析以为 flag 是:wojiushidaan,结果发现输入还是错误。于是尝试删除反调试的逻辑
一开始将这个循环体的条件设置成了不进入循环体,结果发现输入 wojiushidaan 竟然对了。
但是反调试却过不去,最后明白这儿不是反调试的地方。

反复测试发现只是在 sub_17F4(v6)这个函数进行的反调试,于是将这一步 nop 掉了

最后调试发现wojiushidaan的字符串被替换成了aiyou,bucuoo,替换的代码正是在JNI_Onload
一开始的循环体操作的,因此静态分析以为 flag 是 wojiushidaan,结果实际上是在加载 so
之后被修改成了 aiyou,bucuoo,因此这个才是正确答案

解题成功:

项目11-过反调试解ctf
又是一道CTF题目,这道CTF题目支持x86,里面有个x86的so
我直接 x86 搞
首先发现的反 23946 端口的逻辑是在 ad 函数中,该函数是在 jni_onload 里首先调用的

nop 掉后就可以开始调试了,经过多次调试才注意到 ds 函数中的第 2 个 call,即如下图的红
框部分,这个就是正确的密码,只不过是密文,需要 wolf_de 函数解密


反复调试 ds 函数,发现如下绿框才是真正的关键所在,这部分代码决定后续逻辑走向,决
定着是走向密码错误还是走向密码正确。

如图的红框底下的 cmp ecx,edx,只有当 ecx 和 edx 的值相等的时候才说明密码正确,才会走
向密码正确的时候的处理分支从而进入 jk 函数,最终提示一句英文恭喜。
