做过的项目案例

安卓逆向经验

项目1-大鹅(共存)

用的MT,操作过程如下:

>一键共存,修改包名

>去除签名校验

>去除资源混淆

>查看进入,找到资源文件以翻译模式替换掉app_name的名称

>查看进入,找到icon.png,以新的图片替换掉原始图片,名称和文件格式保持一致

>再一次去除资源混淆(不再来一下发现在mt管理器中显示的apk图标还未更新,再来一下就更新了)

分身共存、改名和改图标后的截图:

%title插图%num

项目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项

%title插图%num
使用goto大法在cn.wsds.gamemaster.ui包中的ActivityAccelMode将198行下面添加goto :goto_0即可
%title插图%num

项目4-白嫖ESFileExplore的VIP

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

%title插图%num

最终如下图:

%title插图%num

项目5-破解单机斗地主支付

修改一个地方即可,com.june.game.doudizhu.activities.b.v.a方法

%title插图%num

将图中的代码替换成new w(this).a(“伪造的支付响应结果”); 

我伪造的响应结果:resultStatus={9000};memo={9000};result={200}

resultStatus必须是9000,其它的是我随意填的,不要问为什么,历经几小时分析出的。        new w(this).a(“伪造的支付响应结果”)的smali代码就不贴了

%title插图%num

项目6-破解愤怒的小鸟实名认证和支付

去实名认证有2种方法:

其一:

com.xiaomi.gamecenter.sdk.web.VerifyIDWebFragment.onKey

%title插图%num

注释掉它,这样实名认证的弹窗都不会出现的

其二:

com.xiaomi.gamecenter.sdk.web.VerifyIDWebFragment.codeDeal

将switch接收的值改成恒等于200即可,这样的方式实名认证弹窗会出现,但是允许关闭它,关闭后就不会再出现。

%title插图%num
支付破解也修改一个地方即可

com.rovio.rcs.payment.talkweb.TalkwebPaymentProvider$1$2.onResponse

将switch接收的值改成恒等于9999即可

%title插图%num

最终效果:

%title插图%num

项目7-破解消灭星星支付

需要先去掉华为移动服务弹窗阻止

com.huawei.hms.common.internal.BaseHmsClient.connect

%title插图%num

注释掉它即可

破解支付:

com.brianbaek.popstar.popStarA$3.handleMessage

1,去掉视频未准备的逻辑

%title插图%num
2,跳过支付环节直接调用InvitedBox.show(true)和InvitedBox类中的私有方法:handleData方法并传入购买的币数量参数
%title插图%num

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

%title插图%num

项目8-基于Xposed编写插件破解滑雪大冒险游戏

1,经过ddms分析点击购买事件,找到所在类为

com.yodo1.android.sdk.helper.Yodo1PayHelper$6$1$1

2,在jeb中分析发现最终购买成功与否的处理方法是该类里的onResult方法

%title插图%num

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

%title插图%num

让逻辑直接走这儿,不经过switch方法便可成功购买。

结合上下文分析如果要想用xposed 进行hook不走switch方法,那么传入switch方法的参数得为假,但是errorCode得等于0xd0,即208 ,那么最终hook代码如下:

%title插图%num

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

%title插图%num

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

%title插图%num

项目9-基于Xposed编写插件破解赛车向前冲游戏

通过ddms发现点击购买时的类是cn.egame.terminal.paysdk.EgamePayJD$2$1,对应的方法是onClick,如下图

%title插图%num

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

%title插图%num

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

%title插图%num

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

%title插图%num

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

%title插图%num

项目10-过反调试解ctf

题目是阿里聚安全的一道题目

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

%title插图%num

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

%title插图%num

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

%title插图%num

解题成功:

%title插图%num

项目11-过反调试解ctf

又是一道CTF题目,这道CTF题目支持x86,里面有个x86的so

我直接 x86 搞
首先发现的反 23946 端口的逻辑是在 ad 函数中,该函数是在 jni_onload 里首先调用的

%title插图%num

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

%title插图%num
解密后 v3 的值将暂存于 eax 寄存器,如下图:
%title插图%num

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

%title插图%num

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

%title插图%num