找到
8
篇与
安卓逆向
相关的结果
- 第 2 页
-
安卓逆向-泡泡聊天 一、泡泡聊天 1. 逆向前置 1.1 逆向目标 目标:账号密码登录 版本:v1.7.4 时间:2024-06-10 1.2 简要分析 关于泡泡聊天比较重要的就是抓包证书以及360的壳,直接抓包结果如下: 这是做了服务端证书的校验,所以我们需要拿到证书并且导入到charles,具体步骤如下: - 获取 bks 或 p12证书 文件 - 获取证书相关密码 - 将证书导入到charles,可以实现抓包(bks格式需要转换p12格式) - 用requests发送请求时,携带证书去发送请求2. 逆向实现 2.1 获取证书&密码 使用frida脚本进行hook相应的证书与密码,这是上述步骤的前两步; 可以看到加载了不止一个证书,这是正常的,但是只有一个有密码,所以无疑就是这个了; 那么它的证书就在asset目录下的bks文件;那么此时我们并不知道具体是哪一个bks文件,所以在此直接保存当前获取到的流文件并且保存下来; Java.perform(function () { var KeyStore = Java.use("java.security.KeyStore"); var String = Java.use("java.lang.String"); KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (inputStream, v2) { var pwd = String.$new(v2); console.log('\n--------------------------------------------------------------') console.log("密码:" + pwd, this.getType()); if (this.getType() === "BKS") { var myArray = new Array(1024); for (var i = 0; i < myArray.length; i++) { myArray[i] = 0x0; } var buffer = Java.array('byte', myArray); var file = Java.use("java.io.File").$new("/sdcard/Download/paopao-" + new Date().getTime() + ".bks"); var out = Java.use("java.io.FileOutputStream").$new(file); var r; while ((r = inputStream.read(buffer)) > 0) { out.write(buffer, 0, r); } console.log("save success!") out.close() } var res = this.load(inputStream, v2); return res; }; }); // frida -U -f com.paopaotalk.im -l 2.hook_save.js 在进行这一步之前需将app赋予可读写的权限;随后去观察是否保存成功; 至此证书以及密码我们获取到了;后续就需要导出证书并且导入到charles; 2.2 导出&导入证书 扩展:有时进行hook时会失败,提示找不到类等,是因为有壳导致,可以加上延迟; 此时我们需要将bks证书转换成p12的格式,才支持;使用portecle-1.11工具; 将选择的文件导出,选择p12格式; 此时根据指引输入密码后即可导出; 接下来即可将证书导入到charles中; 选择客户端证书,并且添加; 导入p12证书; 端口等选择所有; 接下来再度抓包即可成功; 2.3 代码实现 具体逻辑比较简单,脱壳使用frida-dumpdex大致实现如下: frida-dexdump -U -f 包名# File:01-泡泡聊天登录实现.py # Author:下雨天 # Date:2024-06-10 21:50 import hashlib import random from requests_pkcs12 import get, post import json import uuid import urllib3 urllib3.disable_warnings() def md5_hash(password): md5_obj = hashlib.md5() md5_obj.update(password.encode('utf-8')) md5_hash = md5_obj.hexdigest() return md5_hash def double_md5_hash(password, openId): # 第一次MD5加密 md5_obj = hashlib.md5() md5_obj.update(password.encode('utf-8')) first_md5_hash = md5_obj.hexdigest() # 将第一次MD5加密结果与openId和"MOSGRAM"拼接起来 combined_string = first_md5_hash + openId + "MOSGRAM" # 第二次MD5加密 md5_obj = hashlib.md5() md5_obj.update(combined_string.encode('utf-8')) second_md5_hash = md5_obj.hexdigest() return second_md5_hash def login(): password = "aa12121323" openId = "008615222225555" timestamp = random.randint(100, 999) headers = { "bundle_id": "com.paopaotalk.im", "version": "1.7.4", "timestamp": str(timestamp), "sign": md5_hash(str(timestamp)), "app_id": "qiyunxin", "Accept-Language": "zh-CN", "package": "com.paopaotalk.im", "Content-Type": "application/json; charset=UTF-8", "Host": "api.vvchat.im", "User-Agent": "okhttp/4.8.1" } url = "https://api.vvchat.im/userservices/v2/user/login" device_id = md5_hash(str(uuid.uuid4()).replace("-", "")) data = { "device_type": "app", "username": openId, "password": double_md5_hash(password, openId), "device_id": device_id, "device_name": "Google Pixel 2 XL", "device_model": "Pixel 2 XL" } data = json.dumps(data, separators=(',', ':')) response = post(url, headers=headers, data=data, pkcs12_filename='Client.p12', pkcs12_password='111111', verify=False ) print(response.text) print(response) if __name__ == '__main__': login() 值得注意的就是发送请求时依然需要携带证书;
-
安卓逆向-Native层相关HOOK 一、Native层相关hook 1. hook_so层 hook_so层只需要得到它的函数地址,有函数地址就能hook与主动调用,而得到函数地址的方式有两种; 1.1 方式一 通过frida提供的api来得到,该函数必须有符号的才可以; 有符号是指此函数是否出现在导出、导入、符号表里; 1.2 方式二 通过计算得到地址:so基址+函数在so中的偏移[+1](32位+1) 2. 各种枚举 2.1 枚举导入表 通过枚举导入表,可以得到出现在导入表中的函数地址;(enumerateImports) var imports = Module.enumerateImports("libifeng_secure.so"); // console.log(JSON.stringify(imports[0])) for (var i = 0; i < imports.length; i++) { // if (imports[i].name == "atoi") { // console.log(JSON.stringify(imports[i])); console.log('导入函数名--->>>',imports[i].name); console.log('导入函数地址--->>>',imports[i].address); console.log('-----------------------------------------------------------------') // break; // } } 以某so为例,得到的结果如下: 2.2 枚举导出表 通过枚举导出表,可以得到出现在导出表中的函数地址,与导入表同理,api不同;(enumerateExports) var imports = Module.enumerateExports("libifeng_secure.so"); // console.log(JSON.stringify(imports[0])) for (var i = 0; i < imports.length; i++) { // if (imports[i].name == "atoi") { // console.log(JSON.stringify(imports[i])); console.log('导出函数名--->>>',imports[i].name); console.log('导出函数地址--->>>',imports[i].address); console.log('-----------------------------------------------------------------') // break; // } } 以某so为例结果如下: 2.3 枚举符号表 通过枚举符号表,可以得到出现在符号表中的函数地址; var symbols = Module.enumerateSymbols("libifeng_secure.so"); // console.log(JSON.stringify(symbols[0])) for (var i = 0; i < symbols.length; i++) { console.log('符号表函数名--->>>',symbols[i].name); console.log('符号表函数地址--->>>',symbols[i].address); console.log('-----------------------------------------------------------------') }2.4 枚举模块 通过枚举模块,再枚举模块里面的导出表,可以快速找到某个导入函数出自哪个so; // 枚举进程中已加载的模块 var modules = Process.enumerateModules(); var module = modules[0].enumerateExports() for(let i = 0; i < module.length; i++){ console.log('枚举进程函数名--->>>',module[i].name); console.log('枚举进程函数地址--->>>',module[i].address); console.log('-----------------------------------------------------------------') } // console.log(JSON.stringify(modules[0].enumerateExports())); 通过枚举模块得到的是一个数组,取第一个后就可以调用上述三个方法; 3. hook导出函数 在so导出表里的函数,可以通过frida提供的api来获取函数地址,Module.findExportByName("xxxx.so", "add"),函数名以汇编中出现的为准; // 导出函数的hook var funcAddr = Module.findExportByName("libencryptlib.so", "_ZN7MD5_CTX11MakePassMD5EPhjS0_"); console.log('函数地址--->>>',funcAddr); Interceptor.attach(funcAddr, { onEnter: function (args) { console.log("funcAddr onEnter args[1]: ", hexdump(args[1])); console.log("funcAddr onEnter args[2]: ", args[2].toInt32()); this.args3 = args[3]; }, onLeave: function (retval) { console.log("funcAddr onLeave args[3]: ", hexdump(this.args3)); } }); 在这里以口袋48为例: 在这里我们的前置条件是,传了三个参数,这里初始打印第三个参数时,发现结果全是0,这里大概就是一个缓冲区,这里是因为C语言里,非常喜欢将参数当作返回值使用,那么这里我们就需要在离开的时候再读取内存; this.args3 = args[3]; console.log("funcAddr onLeave args[3]: ", hexdump(this.args3)); 在这里我们知道它是MD5,我们对比一下结果; 可以发现与hook到的结果是一致的; 4. 获取模块基址 在此的前提是,我们需要的函数不在三个表里,我们就没办法直接使用frida提供的api来获取函数地址,在此需要计算它的函数地址; 计算公式如下: so基址+函数在so中的偏移[+1] 32位则 +1 因此,我们需要先得到so基址,也就是模块基址; 4.1 findModuleByName 使用findModuleByName时,指明so文件即可,得到的是一个module对象,可以进行转换,也可以直接.base取地址; var module1 = Process.findModuleByName("libencryptlib.so"); console.log("module1对象--->>>",JSON.stringify(module1)); console.log("module1基址--->>>", module1.base); 直接.base取的就是基址; 4.2 getModuleByName 与findModuleByName类似,得到的也是一个对象; var module2 = Process.getModuleByName("libencryptlib.so"); console.log("module2对象--->>>",JSON.stringify(module2)); console.log("module2基址--->>>", module2.base); 结果如下: 4.3 findBaseAddress(推荐) 与前两个有所不同,这里得到的直接就是函数地址,也就是基址; var soAddr = Module.findBaseAddress("libencryptlib.so"); console.log("soAddr基址--->>>", soAddr); 这里就无需再去操作,返回值就是地址; 4.4 enumerateModules 通过枚举所有模块,再判断是否与我们需要的模块一样,如果一致则输出地址等; var modules = Process.enumerateModules(); for(let i = 0; i < modules.length; i++){ if(modules[i].name == "libencryptlib.so"){ console.log(modules[i].name + " " + modules[i].base); } } 不过此方式用的较少; 也可以通过地址找模块,这也就可以调用一些方法; var module = Process.findModuleByAddress(Module.findBaseAddress("libencryptlib.so")); console.log("module " + module.name + " " + module.base);5. 函数地址计算 5.1 偏移 上述描述可知,函数地址计算如下: so基址+函数在so中的偏移[+1] [ 32位则 +1] 基址我们已经能够获取到了,那就剩下偏移; 以口袋48app为例,这里我们需要找某函数的偏移; 在其定义位置按下tab,则可转到汇编,界面如下: 实际上这就是它的偏移,它是相对so基址的偏移;得到这个偏移就可以计算出函数地址了; 在这里是否+1呢,在目前来说,大部分是64位的so,此时则不需要加,若为32位则需要加; 5.2 地址计算 依据公式,首先得到基址,再加上偏移; var soAddr = Module.findBaseAddress("libencryptlib.so"); console.log(ptr(soAddr).add(0xxx)); soAddr得到是基址,其实也就是指针,在这里通过add (sub方法为减) 加上偏移,ptr实际上就是指针,也可以不加ptr; 而add里面是一个数值,不是字符串,这里是十六进制,则需要加上0x; 何时加ptr呢,若你的soAddr为具体的数值,则需要加,如: var soAddr = Module.findBaseAddress("libencryptlib.so"); var so = 0x72777a6000; console.log("soAddr基址--->>>" + soAddr); console.log(ptr(so).add(0x1FA38)); // new NativePointer() == ptr() 在一个具体的数值add时,由于不是指针,则无法调用add方法,加上ptr即可,也是同样可以得到地址的; 6. hook任意函数 根据上述条件,我们已经可以hook任意的函数了,得到一个地址即可; var soAddr = Module.findBaseAddress("libencryptlib.so"); // var so = 0x72777a6000; console.log("soAddr基址--->>>" + soAddr); // console.log(ptr(so).add(0x1FA38)); // new NativePointer() var funcAddr = soAddr.add(0x1FA38); console.log("funcAddr函数地址--->>>" + funcAddr); Interceptor.attach(funcAddr, { onEnter: function (args) { console.log("funcAddr onEnter args[1]: ", hexdump(args[1])); console.log("funcAddr onEnter args[2]: ", args[2].toInt32()); this.args3 = args[3]; }, onLeave: function (retval) { console.log("funcAddr onLeave args[3]: ", hexdump(this.args3)); } }); 其余就与hook导出函数是类似的;