吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2304|回复: 21
收起左侧

[Android 原创] Android 自动化分析之 Activity 对应 am 命令提取

[复制链接]
p1s1lver 发表于 2023-4-7 18:40
编写 Android 自动化代码时,不想进行太多的 UI 操作,通常使用 start activity 的方式 (等价于 am 命令) 直接跳转到对应界面,如何快速获得这个 am 命令呢?

这里封装了 hook 逻辑和转换逻辑,本质上是对 Activity 对应的 Intent 对象进行了规则的提取和转换,这里我绘制了一张逻辑图,只需要 hook 之后点击界面即可获得这个界面的 am 启动命令。
有一种特殊情况,是界面本身通过 Fragment 实现而不是 Activity 实现,这种方式本身就不能通过 am 命令实现。

image_1680860454809_0.png

就是,只要结果输出了,那么就是可用的 am 命令。
记得删除其中的一些无用的东西,如带有时间戳信息的可能会被 onCreate 方法中的逻辑判断为过期,从而崩溃;
也可以替换其中的一些东西,比如一个 app 的搜索页,可以替换为默认关键词,从而可以节省一步 set_text

image_1680848776242_0.png


[JavaScript] 纯文本查看 复制代码
function intentToAmCommand(intent) {
    let amCommand = "am start"
    let action = intent.getAction()
    let categories = intent.getCategories()
    let type = intent.getType()
    let component = intent.getComponent()
    let flags = intent.getFlags()
    let extras = intent.getExtras()

    if (action !== null) {
        amCommand += ` -a ${action}`
    }
    if (categories && categories.length > 0) {
        categories.forEach(category => {
            amCommand += ` -c ${category}`
        })
    }
    if (type !== null) {
        amCommand += ` -t ${type}`
    }
    if (component !== null) {
        amCommand += ` -n ${component.getPackageName()}/${component.getClassName()}`
    }
    if (flags !== 0) {
        amCommand += ` -f ${flags}`
    }
    if (extras !== null) {
        amCommand += ` -e`
        let iter = extras.keySet().iterator()
        while (iter.hasNext()) {
            let key = iter.next()
            let value = extras.getString(key)
            amCommand += ` "${key}" "${value}"`
        }
    }
    return amCommand
}

function logIntentAm(intent) {
    // 打印对应 am 命令
    console.log(`[*] am command: ${intentToAmCommand(intent)}`)  // 后续自行删改
}

function hookOnCreate() {
    let Activity = Java.use("android.app.Activity");
    console.log("[*] hooked Activity onCreate method");
    Activity.onCreate.overload('android.os.Bundle').implementation = function (arg1) {
        let theIntent = this.getIntent()
        logIntentAm(theIntent)
        return this.onCreate(arg1)
    };
}

setTimeout(() => {
    Java.perform(() => {
        console.log("----- FRIDA start mocking -----");
        try { hookOnCreate(); } catch (err) { console.error(err.stack); }
        console.log("----- FRIDA finish mocking ----");
    })
}, 2000)
image.png

免费评分

参与人数 7吾爱币 +12 热心值 +7 收起 理由
chinawolf2000 + 1 + 1 热心回复!
sorryzzital + 1 + 1 谢谢@Thanks!
allspark + 1 + 1 用心讨论,共获提升!
junjia215 + 1 + 1 谢谢@Thanks!
JaniQuiz + 1 我很赞同!
Huang210 + 1 + 1 热心回复!
正己 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| p1s1lver 发表于 2023-4-8 11:32
本帖最后由 p1s1lver 于 2023-4-8 12:01 编辑

不少的自动化工具中会自己封装 startActivity 方法,这些方法相较于 am 命令,不会将大部分时间都放在虚拟机初始化上,性能更高,但是对应的 IntentFilter 机制的原理是没有变的,以如下代码为例:

构造一个 JavaScript 函数打印出构造的 python 语句字符串如打开开发者选项设置是 device.start_activity(action="android.settings.APPLICATION_DEVELOPMENT_SETTINGS")

这个 start_activity 方法的参数规则对应 am 命令的规则如下:
- action 参数对应于 am 命令中的 -a (action)
- category 参数对应 am 命令中的 -c (category)
- component 参数对应 am 命令中的 -n (component)
- extras 参数(dict) 对应 am 命令中的 -e 参数

现在在代码中也封装了对应的 python 语句的打印,这样就省去所有的步骤了,有就是能用,没有就是非 activity 启动,打印的代码及如何删除无用逻辑举例如下图:

Snipaste_2023-04-08_11-31-57.png

代码如下:
[JavaScript] 纯文本查看 复制代码
function constructAmCommand(action, categories, component, extras) {
    let amCommand = "am start"
    if (action !== null) {
        amCommand += ` -a ${action}`
    }
    if (categories && categories.length > 0) {
        categories.forEach(category => {
            amCommand += ` -c ${category}`
        })
    }
    if (component !== null) {
        amCommand += ` -n ${component.getPackageName()}/${component.getClassName()}`
    }
    if (extras !== null) {
        amCommand += ` -e`
        let iter = extras.keySet().iterator()
        while (iter.hasNext()) {
            let key = iter.next()
            let value = extras.getString(key)
            amCommand += ` "${key}" "${value}"`
        }
    }
    return amCommand
}


function constructPythonStatement(action, categories, component, extras) {
    let params_dict = {}
    if (action !== null) {
        params_dict["action"] = action
    }
    if (categories && categories.length > 0) {
        params_dict["categories"] = categories
    }
    if (component !== null) {
        params_dict["component"] = `${component.getPackageName()}/${component.getClassName()}`
    }
    if (extras !== null) {
        let extras_params_dict = {}
        let iter = extras.keySet().iterator()
        while (iter.hasNext()) {
            let key = iter.next()
            let value = extras.getString(key)
            if (value !== null) {
                extras_params_dict[key] = value
            }
        }
        params_dict["extras"] = extras_params_dict
    }
    let params = Object.entries(params_dict).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join(", ")
    let pythonStatement = `device.start_activity(${params})`
    console.log(pythonStatement)
}

function printAmAndPython(intent) {
    let am_command = constructAmCommand(intent.getAction(), intent.getCategories(), intent.getComponent(), intent.getExtras())
    let python_statement = constructPythonStatement(intent.getAction(), intent.getCategories(), intent.getComponent(), intent.getExtras())
    console.log(am_command)
    console.log(python_statement)
}

function hookOnCreate() {
    var Activity = Java.use("android.app.Activity");
    Activity.onCreate.overload('android.os.Bundle').implementation = function (arg1) {
        var theIntent = this.getIntent()
        printAmAndPython(theIntent)
        constructPythonStatement(theIntent.getAction(), theIntent.getCategories(), theIntent.getComponent(), theIntent.getExtras())
        return this.onCreate(arg1)
    };
}

setTimeout(() => {
    Java.perform(() => {
        console.log("[~] Inject FRIDA ...");
        try {
            hookOnCreate();
        } catch (err) { console.error(err.stack); }
        console.log("[-] FRIDA end mocking.");
    })
}, 2000)
 楼主| p1s1lver 发表于 2023-4-7 18:48
 楼主| p1s1lver 发表于 2023-4-8 11:03
原理上,基于 IntentFilter 机制的 am 命令启动 activity 只需要 -a -c -n -es -ei 这些参数即可,其中 -es 和 -ei 参数可以直接简化为 -e 参数 (不显式指定是 String 还是 Int 类型),所以修改了一下代码,减少对 -t 和 -f 参数的判断

[JavaScript] 纯文本查看 复制代码
function intentToAmCommand(intent) {
    let amCommand = "am start"
    let action = intent.getAction()
    let categories = intent.getCategories()
    let component = intent.getComponent()
    let extras = intent.getExtras()

    if (action !== null) {
        amCommand += ` -a ${action}`
    }
    if (categories && categories.length > 0) {
        categories.forEach(category => {
            amCommand += ` -c ${category}`
        })
    }
    if (component !== null) {
        amCommand += ` -n ${component.getPackageName()}/${component.getClassName()}`
    }
    if (extras !== null) {
        amCommand += ` -e`
        let iter = extras.keySet().iterator()
        while (iter.hasNext()) {
            let key = iter.next()
            let value = extras.getString(key)
            amCommand += ` "${key}" "${value}"`
        }
    }
    return amCommand
}

function hookOnCreate() {
    var Activity = Java.use("android.app.Activity");
    Activity.onCreate.overload('android.os.Bundle').implementation = function (arg1) {
        var theIntent = this.getIntent()
        console.log(` am command: ${intentToAmCommand(theIntent)}`)
        return this.onCreate(arg1)
    };
}

setTimeout(() => {
    Java.perform(() => {
        console.log("[~] Inject FRIDA ...");
        try {
            hookOnCreate();
        } catch (err) { console.error(err.stack); }
        console.log("[-] FRIDA end mocking.");
    })
}, 2000)


另外,在 am 命令的输出中,需要删除的不仅仅是 -e 参数中多余的参数,还有参数(比如 url,即 Android deep linking 的 url)本身可能包含了关于时间戳和 navigator 的信息,这些信息也应该删除,否则被逻辑检测到往往会闪退,如下
[Shell] 纯文本查看 复制代码
am start -a com.xxx.xxx.navigator.xxx -n com.xxx.xxx/com.xxx.xxx.module.search.searchResult.xxxSearchResultActivity -e "key_intent_fdsfas_dsdfasdfata" "null" "com.xxx.xxx.navigator.url"  "xxx://page.xxx/search?q=%E5%A5%B6%E7%B2%89&searchType=default&_input_charset=utf-8&sdfadfp=a1z60.7757874.dsfadsfdfsadsf.5_%E5%A5%B6%E7%B2%89" "asdfasdfasdf" "21108695,7757874," "afsdfadfadsfadsfadf" "null" "fasdfewfaesfgqerg" "null"

就应该被简化为下面的命令格式:
[Shell] 纯文本查看 复制代码
am start -a com.xxx.xxx.navigator.xxx -n com.xxx.xxx/com.xxx.xxx.module.search.searchResult.xxxSearchResultActivity -e "com.xxx.xxx.navigator.url" "xxx://page.xxx/search?q=%E5%A5%B6%E7%B2%89"

前者打开会因为时间戳和 navigator 相关信息被检测到然后闪退,后者是可以直接打开对应的搜索页面的。
头像被屏蔽
QAQ666 发表于 2023-4-8 13:02
提示: 作者被禁止或删除 内容自动屏蔽
sorryzzital 发表于 2023-4-9 15:27
写的真是详细,图文并茂,谢谢!
JordanYang 发表于 2023-4-9 18:49
真的很详细,感谢大佬分享!
MMM2042 发表于 2023-4-9 20:55
感谢分享!很详细
cqymm 发表于 2023-4-10 10:12
这个应该怎么运行啊。有没有大佬教教啊
 楼主| p1s1lver 发表于 2023-4-10 10:41
xiaoshuis16now 发表于 2023-4-9 23:06
这种方法,相当于是在跑自动化前,需要自己先手动全部界面点击一遍是不?

是的,这样的话自动化就不要写很多 ui 对应的代码了,而是直接进入对应界面。
但正如文中所写,fragment 实现的只能通过 ui 点击运行,不过现在大多数的 app 都是通过 activity 界面运行的
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-3-28 18:14

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表