app逆向笔记(未完结)(小白记录大佬勿入)【2023.4.19更新】

查看 86|回复 9
作者:418   
常用基础
>>> adb shell
>>> su
>>> cd /data/local/tmp
>>> ./frida
>>> adb forward tcp:27042 tcp:27042
>>> adb forward tcp:27043 tcp:27043
run hook script
  >>> -o xx.txt  hook保存日志到本地txt
    ①运行ps查看手机端进程列表
    frida-ps -R
    ②附加某个进程
    frida -R com.demo.fridahook
    ③-> 写脚本
    这种模式重启app, 停在了开头 需要输入 %resume 继续
    frida -U -f com.gdufs.xman -l hook_saveSN.js
    命令行附加应用进程然后敲代码进程注入
    frida -U -f com.demo.fridahook
    附加模式 (attach)
    应用不会重启 从执行命令起注入
    frida -U -l hook.js 15.2.2版不要写包名 要写应用名称
    frida -UF -l hook.js  这就可以了 后面不用写名称
    重启一个Android进程并注入脚本
    frida -U -l myhook.js -f com.xxx.xxxx --no-pause
    spawn 模式 15.2.2版本不可以这样写
    frida -Uf com.xxx.xxxx -l hook.js
    ①查看现在的进程
    frida-ps -R
    ②用pid附加上去  可以实时在js文件中修改脚本 控制台会输出错误语法的日志 不用管
    frida -U -p [PID] -l hook_saveSN.js
adb无线连接手机(不需要在同一wifi下) 校园网无效
>>> adb tcpip 5555
>>>adb connect 192.168.100.20
找进程名和pid
# 枚举所有的进程
processes = rdev.enumerate_processes()
for process in processes:
    print(process)
# 获取在前台运行的APP
# Application(identifier="com.che168.autotradercloud", name="车智赢+", pid=3539, parameters={})
front_app = rdev.get_frontmost_application()
print(front_app)
搜索tips
看Node, 如果不是该app相关包可以无视
treeMap.put(xxx)
search
sign  "sign"  &sign=  &sign  sign=
搜索同一请求的其它有个性的关键字
搜索独有信息
网址 拿后缀搜       retrofit发送的请求,找reportClick 查找用例,或直接搜索"reportClick"
hook拦截器/TreeMap/StringBuilder定位

如果是加密的数据,看它长得像哪种加密方式,hook验证,打印调用栈追踪
有用的网址
https://1024tools.com/hash 各种加密 方便查看
https://curlconverter.com/  curl转其它语言
python TOOLS
端口转发一键运行
import subprocess
# 重新连接手机需要运行
subprocess.getoutput("adb forward tcp:27042 tcp:27042")
subprocess.getoutput("adb forward tcp:27043 tcp:27043")
切割?后的东西
形如 aa=11&bb=22&cc=33...xx=xx&xxx=xxx
param_string = input(">>>")
import json
data_dict = {item.split('=')[0]: item.split('=')[1] for item in param_string.split('&')}
data_string = json.dumps(data_dict, indent=4)
print(data_string)
with open("output.txt", mode="w") as f:
    f.write(data_string)
java字节数组(byte)转字符串
byte_list = [97,110,99,104,111,114,82,101,112,108,121,73,100,48,99,111,110,116,101,110,116,73,100,56,56,57,49,55,52,53,48,99,111,110,116,101,110,116,84,121,112,101,49,108,97,115,116,73,100,108,105,109,105,116,50,48,108,111,103,105,110,84,111,107,101,110,112,108,97,116,102,111,114,109,97,110,100,114,111,105,100,115,111,117,114,99,101,116,105,109,101,115,116,97,109,112,49,54,54,55,57,57,57,48,52,52,57,49,56,117,117,105,100,48,100,57,54,56,54,97,55,56,101,50,97,97,57,55,53,118,53,46,48,46,54]
def byte2str(byte_list):
    data = bytearray()
    for i in byte_list:
        data.append(i)
    data_string = data.decode('utf-8')
    print(data_string)
byte2str(byte_list)
# anchorReplyId0contentId88917450contentType1lastIdlimit20loginTokenplatformandroidsourcetimestamp1667999044918uuid0d9686a78e2aa975v5.0.6
java字节数组(有符号) -> python字节数组(无符号)
byte_list = [47,-38,-99,34,-13,44,-43,-119,3,76,8,32,47,-115,105,61,-91,-46 ...]
bs = []
for item in byte_list:
    if item
字节 -> 十六进制(Hex)字符串
bytes_data = b'\x9f\x1bVbf\x12\xa73\x91\xe5\x90\xb3fN\xe6\xfb'
#       去除前面的0x 不满两位补0
result = "".join([hex(item)[2:].rjust(2, "0") for item in bytes_data])
print(result)
字节数组 -> 十六进制(Hex)字符串
byte_list = [47, 218, 157, 34, 243, 44, 213, 137, 3, 76, 8, 32, 47, 141, 105, 61, 165, 210]  # 注意java的要先处理成python的
print([hex(ele)[2:] for ele in byte_list])
随机生成mac地址
def create_random_mac(sep=":"):
    """ 随机生成mac地址 """
    data_list = []
    for i in range(1, 7):
        part = "".join(random.sample("0123456789ABCDEF", 2))
        data_list.append(part)
    mac = sep.join(data_list)
    return mac
字符串 -> 字节
data_string = "啊吧啊吧"
data_string.encode('utf-8')
字典排序后拼接成x=x&xx=xx
data_dict = {'x':'x', "xx":"xx"}
ordered_string = "&".join(["{}={}".format(key, data_dict[key]) for key in sorted(data_dict.keys())])
python连接frida-server的方式
usb连接
# 获取设备信息-----------------------
rdev = frida.get_remote_device()
session = rdev.attach("抖音短视频")
# ---------------------------------
端口连接
>>> ./frida -l 0.0.0.0:8888
device = frida.get_device_manager().add_remote_device("192.168.x.x:8888") # 手机ip
session = device.attach("抖音短视频")  # 包名或名字  attach附加模式 不用重启app
pid = device.spawn(["com.xx.xx"])  # 包名或名字  spawn模式 重启app
session = device.attach(pid)
wifi连接
先连着usb线 连接玩后可以断开
>>> adb tcpip 5555
>>> adb connect 192.168.100.20 手机i
模板
import frida
import sys
# ---------------------------------
rdev = frida.get_remote_device()
pid = rdev.spawn(["com.xx.xx"])
session = rdev.attach(pid)
# ---------------------------------
scr = """
Java.perform(function () {
        var ClassName = Java.use('com.xxx.xx.ClassName');
        ClassName.Method.implementation = function(arg1, arg2, ...) {
                result = this.Method(arg1, arg2, ...);
                return result;
        }
})
"""
script = session.create_script(scr)
def on_message(message, data):
    print(message, data)
script.on("message", on_message)
script.load()
rdev.resume(pid)  # spawn
sys.stdin.read()  # 程序阻塞 不让停止
frida rpc 主动调用
在"""里加载
格式一
import frida
def get_frida_rpc_script():
    rdev = frida.get_remote_device()
    session = rdev.attach("猿人学2022")
    scr = """
    function invokeSign(data){
    var result;
        Java.perform(function () {
            Java.choose("com.yuanrenxue.match2022.security.Sign",{
                onMatch:function(ins){  // 实例化对象 可能需要刷新一下手机页面加载对象
                    console.log("ins=>",ins);
                    result = ins.sign(stringToByte(data));
                },onComplete(){}
            });
        })
        return result;
    }
    rpc.exports = {
        invokesignn:invokeSign,
    }
    """
    script = session.create_script(scr)
    script.load()
    return script
# 调用
script = get_frida_rpc_script()
sign = script.exports.invokesignn(sb)  # exports.后面的名字必须和上面exports{}键的一样 不支持下划线_
格式二
import frida
def get_frida_rpc_script():
    rdev = frida.get_remote_device()
    session = rdev.attach("抖音短视频")
    scr = """
    rpc.exports = {   
        ttencrypt:function(bArr,len){
             var res;
             Java.perform(function () {
                 ......
             return res;
        },
        execandleviathan: function (i2,str){
            var result;
            Java.perform(function () {
                                ......
            });
            return result;
        }
    }
    """
    script = session.create_script(scr)
    script.load()
    return script
# 调用
script = get_frida_rpc_script()
gorgon_byte_list = script.exports.execandleviathan(khronos, un_sign_string)
读取文件加载
import frida
# 不知道干什么用的
def my_message_handler(message, payload):
    print("message=>", message)
    print("payloa=>d", payload)
# connect wifiadb
device = frida.get_device_manager().add_remote_device("192.168.43.71:8888") # 手机ip
print('设备=>', device)
session = device.attach("com.yuanrenxue.match2022")
print('session=>', session)
# load script
with open("app.js") as f:  # app.js见格式一的s
    script = session.create_script(f.read())
script.on("message", my_message_handler)  # 调用错误处理
script.load()
print(script.exports.invokesign('page=' + data['page'] + data['t']))  # 调用
发送请求的格式
什么时候用json
# 请求头content-type: "application/json"  json=json.
requests.post(url, headers=headers, data=data_dict)
treemap有序 在python里怎么处理
# 无序 -> hashmap
data_dict = {
    "_appid": "atc.android",
    "appversion": "2.8.2",
    "channelid": "csy",
    "pwd": md5(passwrod),
    "udid": udid,
    "username": username
}
result = "".join(["{}{}".format(key, data_dict[key]) for key in sorted(data_dict.keys())])
常见加密
python
3DES(对称)
def des3(data_string):
    BS = 8
    pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
    # 3DES的MODE_CBC模式下只有前24位有意义
    key = b'appapiche168comappapiche168comap'[0:24]
    iv = b'appapich'
    plaintext = pad(data_string).encode("utf-8")
    # 使用MODE_CBC创建cipher
    cipher = DES3.new(key, DES3.MODE_CBC, iv)
    result = cipher.encrypt(plaintext)
    return base64.b64encode(result).decode('utf-8')
AES
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64
def aes_encrypt(data_string, key):
    aes = AES.new(
        key=key.encode('utf-8'),
        mode=AES.MODE_ECB,
    )
    raw = pad(data_string.encode('utf-8'), 16)
    return aes.encrypt(raw)
data_string = "明文"
key = "key"
bytes_data = aes_encrypt(data_string, key)  # 字节类型
""" bytes_data
b'\x9f\x1bVbf\x12\xa73\x91\xe5\x90\xb3fN\xe6\xfb'
"""
value = base64.encodebytes(bytes_data)
result = value.replace(b"\n", b'')  # python得到的结果会有\n, java的没有 要注意对比
print(result)
sha256
import hashlib
data = "明文"
salt = "9cafa6466a028bfb"  # 盐
obj = hashlib.sha256()
# 按app的顺序update
obj.update(data.encode('utf-8'))
obj.update(salt.encode('utf-8'))
# --------------------------
res = obj.hexdigest()
print(res)
# e61583f49efa13187b053d2ab1cf2cc8cd99360367f42a6b7d013a49de72108e
md5
def md5(data_string):
    obj = hashlib.md5()
    obj.update(data_string.encode("utf-8"))
    hex_string = obj.hexdigest()
    # print(hex_string)
    return hex_string
java
frida常用脚本
注:用命令行运行的方式貌似不能同时两个脚本运行。。。想要两个脚本同时运行,其中一个用python,另一个用命令行即可;或者都用python执行
  • 记得写Java.perform(function(){...})不要忘了 否则提示找不到类com.xx.xx

    hook
    打印用到的so文件
    使用场景:反调试 看看到了哪里被检测 找到so文件 删掉试试 没大碍就ok 如果不行另寻他路
    Java.perform(function () {
        var dlopen = Module.findExportByName(null, "dlopen"); // 系统文件
        var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
        Interceptor.attach(dlopen, {
            onEnter: function (args) {
                var path_ptr = args[0];
                var path = ptr(path_ptr).readCString();
                console.log("[dlopen:]", path);
            },
            onLeave: function (retval) {
            }
        });
        Interceptor.attach(android_dlopen_ext, {
            onEnter: function (args) {
                var path_ptr = args[0];
                var path = ptr(path_ptr).readCString();
                console.log("[dlopen_ext:]", path);
            },
            onLeave: function (retval) {
            }
        });
    });
    // spawn mode
    // frida -Uf com.xxx.xxxx -l hook.js  15.2.2版本不可使用
    // frida -U -l hook.js -f com.xxx.xxxx --no-pause 用这个代替
    获得调用栈
    console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
    hook拦截器
    使用 JSON.stringify(inter) 把对象是什么打印出来 可以知道是哪些类实例化了拦截器
    Java.perform(function () {
        var Builder = Java.use('okhttp3.OkHttpClient$Builder');
        Builder.addInterceptor.implementation = function (inter) {
            //console.log("实例化:");
            console.log(JSON.stringify(inter));
            //console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
            return this.addInterceptor(inter);
        };
    })
    // spawn 不要用 attach Interceptor可能在app启动的时候加载
    // frida -U -l hook.js -f com.xxx.xxxx --no-pause
    // frida -Uf com.hupu.shihuo -l hook.js -o all_interceptor.txt  15.2.2版本不可用
    输出 eg. 从下往上找   记得手机上点击触发
                ...
                ""
                ""
    Java.perform(function () {
       // ... 根据上面的输出 找到为止
        var a9 = Java.use('cn.shihuo.modulelib.startup.core.c.b');
        a9.intercept.implementation = function (chain) {
            var request = chain.request();
            var urlString = request.url().toString();
            if(urlString.indexOf("https://sh-gateway.shihuo.cn/v4/services/sh-goodsapi/app_swoole_zone/getAttributes/v")!= -1){ // 过滤目标url
                console.log("拦截器9-->", urlString);
            }
            var response = chain.proceed(request); // 不执行当前拦截器 而走下一个拦截器
            return response;
        };
        var a10 = Java.use('cn.shihuo.modulelib.startup.core.c.a');
        a10.intercept.implementation = function (chain) {
            //console.log("拦截器10", chain);
            var request = chain.request();
            var urlString = request.url().toString();
            if(urlString.indexOf("https://sh-gateway.shihuo.cn/v4/services/sh-goodsapi/app_swoole_zone/getAttributes/v") != -1){
                console.log("拦截器10-->", urlString);
            }
            //console.log("拦截器",this.b.value);
            var res = this.intercept(chain);
            //console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
            return res;
        }
    })
    // frida -UF -l hook.js
    hook TreeMap
    Java.perform(function () {
        var TreeMap = Java.use('java.util.TreeMap');
        var Map = Java.use("java.util.Map");
        TreeMap.put.implementation = function (key,value) {
            if(key=="xxx"){ // 根据需要 看抓包
                console.log(key,value);
            }
            var res = this.put(key,value);
            return res;
        }
    });
    hook StringBuilder
    Java.perform(function () {
        var StringBuilder = Java.use("java.lang.StringBuilder");
        StringBuilder.toString.implementation = function () {
            var res = this.toString();
            console.log(res);
            return res;
        }
    });
    hook Base64
    Java.perform(function () {
        var Base64 = Java.use("android.util.Base64");
        Base64.encodeToString.overload('[B', 'int').implementation = function (bArr,val) {
            var res = this.encodeToString(bArr,val);
            console.log("加密了-->",res);
            // console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
            return res;
        }
    });
    hook构造方法
    import javax.crypto.spec.SecretKeySpec;
    ...
    public final class b {
        ...
        public final byte[] a(String body) {
            ...
                SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, "AES"); // hook
        }
    }
    Java.perform(function () {
        var ByteString = Java.use('com.android.okhttp.okio.ByteString');
        var SecretKeySpec = Java.use('javax.crypto.spec.SecretKeySpec');
        SecretKeySpec.$init.implementation = function (key, name) {
            console.lot("key->", ByteString.of(key).utf8());
            var res = this.$init(key, name);
            return res;
        }
    })
    类型转换输出
    map转字符串输出
    Java.perform(function () {
        let RequestUtils = Java.use("com.shizhuang.duapp.common.utils.RequestUtils");
        RequestUtils["c"].implementation = function (map, j2) {
            // 输出参数类型
            console.log(" type of map ->", JSON.stringify(map));
            /*
            * ""
            *            Map 是它的父类泛指子类类型  而真实类型是 HashMap
            * */
            let Map = Java.use('java.util.HashMap');
            let obj = Java.cast(map, Map); // 类型转换
            console.log('c is called' + ', ' + 'map: ' + obj + ', ' + 'j2: ' + j2); // obj.soString()
            let ret = this.c(map, j2);
            console.log('c ret value is ' + ret);
            return ret;
        };
    })
    字节数组转十六进制字符串
    Java.perform(function () {
        let d = Java.use("tv.danmaku.biliplayerimpl.report.heartbeat.d");
        var ByteString = Java.use('com.android.okhttp.okio.ByteString'); // 加上这个
        d["H7"].implementation = function (arg1, ...) {
            let ret = this.H7(arg1, ...);
            console.log('H7 ret value is ' + ret); // ret: [79,-90,...]
            console.log('H7 ret HEX value is ' + ByteString.of(ret).hex()); // 加上这个
            return ret;
        };
    })
    字节转成字符串输出(可以转的前提下)
    var ByteString = Java.use('com.android.okhttp.okio.ByteString'); // 加上这个
    ... var res = this.xxx(...)
            console.log(ByteString.of(res).utf8());
    输出某东西(bVar)是哪个类
    Json.stringfy(bVar)输出如下  console.log(bVar)默认输出[object Object]
                              bVar泛指这个类型
    ""

    字节, 字符串

  • MikeZhang   

    非常有帮助,感谢老哥分享
    xiaoyaowolf   

    看看就是看看。。。
    jjghaa1234   

    这么多笔记,那咋不来点实战?
    mirs   

    感谢大佬分享
    xuri422   


    正己 发表于 2023-4-16 20:42
    这么多笔记,那咋不来点实战?

    版主没发现内容重复了嘛
    pjy612   

    非常好,适合小白入门。
    debug_cat   

    挺好,哪天真要开始涉及这方面的话 能少走不少弯路。。。先Mark了!
    施工加油~
    way1990   

    不错的笔记,收藏啦
    debug_cat   

    谢谢分享,,,,
    您需要登录后才可以回帖 登录 | 立即注册