Android逆向12-RPC
来自吾爱破解-正己
https://www.52pojie.cn/thread-1701353-1-1.html
RPC(Remote Procedure Call Protocol) 是一种远程过程调用协议,允许程序在不同的计算机上请求服务,而无需了解底层网络技术。RPC的主要作用是使不同服务之间的方法调用像本地调用一样便捷
Hook_Libart
libart.so
: 在 Android 5.0(Lollipop)及更高版本中,libart.so
是 Android 运行时(ART,Android Runtime)的核心组件,它取代了之前的 Dalvik 虚拟机。可以在 libart.so
里找到 JNI 相关的实现。
PS:在高于安卓10的系统里,so的路径是/apex/com.android.runtime/lib64/libart.so,低于10的则在system/lib64/libart.so
函数名称 |
参数 |
描述 |
返回值 |
RegisterNatives |
JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods |
反注册类的本地方法。类将返回到链接或注册了本地方法函数前的状态。该函数不应在本地代码中使用。相反,它可以为某些程序提供一种重新加载和重新链接本地库的途径。 |
成功时返回0;失败时返回负数 |
GetStringUTFChars |
JNIEnv*env, jstring string, jboolean *isCopy |
通过JNIEnv接口指针调用,它将一个代表着Java虚拟机中的字符串jstring引用,转换成为一个UTF-8形式的C字符串 |
- |
NewStringUTF |
JNIEnv *env, const char *bytes |
以字节为单位返回字符串的 UTF-8 长度 |
返回字符串的长度 |
FindClass |
JNIEnv *env, const char *name |
通过对象获取这个类。该函数比较简单,唯一注意的是对象不能为NULL,否则获取的class肯定返回也为NULL。 |
- |
GetMethodID |
JNIEnv *env, jclass clazz, const char *name, const char *sig |
返回类或接口实例(非静态)方法的方法 ID。方法可在某个 clazz 的超类中定义,也可从 clazz 继承。GetMethodID() 可使未初始化的类初始化。 |
方法ID,如果找不到指定的方法,则为NULL |
GetStaticMethodID |
JNIEnv *env, jclass clazz, const char *name, const char *sig |
获取类对象的静态方法ID |
属性ID对象。如果操作失败,则返回NULL |
GetFieldID |
JNIEnv *env, jclass clazz, const char *name, const char *sig |
回Java类(非静态)域的属性ID。该域由其名称及签名指定。访问器函数的GetField 及 SetField系列使用域 ID 检索对象域。GetFieldID() 不能用于获取数组的长度域。应使用GetArrayLength()。 |
- |
GetStaticFieldID |
JNIEnv *env,jclass clazz, const char *name, const char *sig |
获取类的静态域ID方法 |
- |
Call<type>Method , Call<type>MethodA , Call<type>MethodV |
JNIEnv *env, jobject obj, jmethodID methodID, .../jvalue *args/va_list args |
这三个操作的方法用于从本地方法调用Java 实例方法。它们的差别仅在于向其所调用的方法传递参数时所用的机制。 |
NativeType,具体的返回值取决于调用的类型 |

Hook_RegisterNatives
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| function find_RegisterNatives(params) { let symbols = Module.enumerateSymbolsSync("libart.so"); let addrRegisterNatives = null;
for (let i = 0; i < symbols.length; i++) { let symbol = symbols[i];
if (symbol.name.indexOf("art") >= 0 && symbol.name.indexOf("JNI") >= 0 && symbol.name.indexOf("RegisterNatives") >= 0 && symbol.name.indexOf("CheckJNI") < 0) { addrRegisterNatives = symbol.address; console.log("RegisterNatives is at ", symbol.address, symbol.name); hook_RegisterNatives(addrRegisterNatives); } } }
function hook_RegisterNatives(addrRegisterNatives) { if (addrRegisterNatives != null) { Interceptor.attach(addrRegisterNatives, { onEnter: function (args) { console.log("[RegisterNatives] method_count:", args[3]);
let java_class = args[1]; let class_name = Java.vm.tryGetEnv().getClassName(java_class);
let methods_ptr = ptr(args[2]); let method_count = parseInt(args[3]);
for (let i = 0; i < method_count; i++) { let name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3)); let sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize)); let fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
let name = Memory.readCString(name_ptr); let sig = Memory.readCString(sig_ptr);
let symbol = DebugSymbol.fromAddress(fnPtr_ptr);
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, " fnOffset:", symbol, " callee:", DebugSymbol.fromAddress(this.returnAddress)); } } }); } }
setImmediate(find_RegisterNatives);
|
hook_GetStringUTFChars
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| function hook_GetStringUTFChars() { var GetStringUTFChars_addr = null; var module_libart = Process.findModuleByName("libart.so"); var symbols = module_libart.enumerateSymbols(); for (var i = 0; i < symbols.length; i++) { var name = symbols[i].name; if ((name.indexOf("JNI") >= 0) && (name.indexOf("CheckJNI") == -1) && (name.indexOf("art") >= 0)) { if (name.indexOf("GetStringUTFChars") >= 0) { GetStringUTFChars_addr = symbols[i].address; } } }
Java.perform(function(){ Interceptor.attach(GetStringUTFChars_addr, { onEnter: function(args){
}, onLeave: function(retval){ console.log("GetStringUTFChars onLeave : ", ptr(retval).readCString()); if(ptr(retval).readCString().indexOf("普通") >=0){ console.log("GetStringUTFChars onLeave : ", ptr(retval).readCString()); console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n'); }
} }) }) } function main(){ Java.perform(function(){ hook_GetStringUTFChars(); }); } setImmediate(main);
|
Hook_Libc
libc.so
: 这是一个标准的 C 语言库,用于提供基本的系统调用和功能,如文件操作、字符串处理、内存分配等。在Android系统中,libc
是最基础的库之一。
类别 |
函数名称 |
参数 |
描述 |
字符串类操作 |
strcpy |
char *dest, const char *src |
将字符串 src 复制到 dest |
|
strcat |
char *dest, const char *src |
将字符串 src 连接到 dest 的末尾 |
|
strlen |
const char *str |
返回 str 的长度 |
|
strcmp |
const char *str1, const char *str2 |
比较两个字符串 |
文件类操作 |
fopen |
const char *filename, const char *mode |
打开文件 |
|
fread |
void *ptr, size_t size, size_t count, FILE *stream |
从文件读取数据 |
|
fwrite |
const void *ptr, size_t size, size_t count, FILE *stream |
写入数据到文件 |
|
fclose |
FILE *stream |
关闭文件 |
网络IO类操作 |
socket |
int domain, int type, int protocol |
创建网络套接字 |
|
connect |
int sockfd, const struct sockaddr *addr, socklen_t addrlen |
连接套接字 |
|
recv |
int sockfd, void *buf, size_t len, int flags |
从套接字接收数据 |
|
send |
int sockfd, const void *buf, size_t len, int flags |
通过套接字发送数据 |
线程类操作 |
pthread_create |
pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg |
创建线程 |
进程控制操作 |
kill |
pid_t pid, int sig |
向指定进程发送信号 |
系统属性查询操作 |
__system_property_get |
const char *name, char *value |
从Android系统属性服务中读取指定属性的值 |
|
uname |
struct utsname *buf |
获取当前系统的名称、版本和其他相关信息 |
|
sysconf |
int name |
获取运行时系统的配置信息,如CPU数量、页大小 |
hook_kill
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function replaceKILL() { var kill_addr = Module.findExportByName("libc.so", "kill"); Interceptor.replace(kill_addr, new NativeCallback(function (arg0, arg1) { console.log("arg0=> ", arg0); console.log("arg1=> ", arg1); console.log('libc.so!kill called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join('\n') + '\n'); }, "int", ["int", "int"])) }
|
hook_pthread_create
1 2 3 4 5 6 7 8 9 10 11 12
| function hook_pthread_create(){ var pthread_create_addr = Module.findExportByName("libc.so", "pthread_create"); console.log("pthread_create_addr: ", pthread_create_addr); Interceptor.attach(pthread_create_addr,{ onEnter:function(args){ console.log(args[0], args[1], args[2], args[4]); },onLeave:function(retval){ console.log("retval is =>",retval) } }) }
|
hook_str_cmp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function hook_strcmp() { var pt_strcmp = Module.findExportByName("libc.so", 'strcmp'); Interceptor.attach(pt_strcmp, { onEnter: function (args) { var str1 = args[0].readCString(); var str2 = args[1].readCString(); if (str2.indexOf("hh") !== -1) { console.log("strcmp-->", str1, str2); this.printStack = true; } }, onLeave: function (retval) { if (this.printStack) { var stack = Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join("\n"); console.log("Stack trace:\n" + stack); } } }) }
|
Hook_Libdl
libdl.so
是一个处理动态链接和加载的标准库,它提供了dlopen
、dlclose
、dlsym
等函数,用于在运行时动态地加载和使用共享库
类别 |
函数名称 |
参数 |
描述 |
动态链接库操作 |
dlopen |
const char *filename, int flag |
打开动态链接库文件 |
|
dlsym |
void *handle, const char *symbol |
从动态链接库中获取符号地址 |
Hook_dlsym
获取jni静态注册方法地址
1 2 3 4 5 6 7 8 9 10 11 12 13
| function hook_dlsym() { var dlsymAddr = Module.findExportByName("libdl.so", "dlsym"); Interceptor.attach(dlsymAddr, { onEnter: function(args) { this.args1 = args[1]; }, onLeave: function(retval) { var module = Process.findModuleByAddress(retval); if (module === null) return; console.log(this.args1.readCString(), module.name, retval, retval.sub(module.base)); } }); }
|
Hook_Linker
Linker是Android系统动态库so的加载器/链接器,通过android源码分析 init 和 init_array 是在 callConstructor 中被调用的
hookInit和hookInitArray
frida hook init_array自吐新解
经过安卓源码比对,从Android 8 ~ 14,结构体中init_array
的位置都很稳定,提取部分头文件信息在CModule中定义一个soinfo结构体,接着定义一个接受一个soinfo
指针参数和一个callback
函数的函数,输出init_array
信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| function hook_call_constructors() { let get_soname = null; let call_constructors_addr = null; let hook_call_constructors_addr = true; let linker = null; if (Process.pointerSize == 4) { linker = Process.findModuleByName("linker"); } else { linker = Process.findModuleByName("linker64"); } let symbols = linker.enumerateSymbols(); for (let index = 0; index < symbols.length; index++) { let symbol = symbols[index]; if (symbol.name == "__dl__ZN6soinfo17call_constructorsEv") { call_constructors_addr = symbol.address; } else if (symbol.name == "__dl__ZNK6soinfo10get_sonameEv") { get_soname = new NativeFunction(symbol.address, "pointer", ["pointer"]); } } if (hook_call_constructors_addr && call_constructors_addr && get_soname) { Interceptor.attach(call_constructors_addr,{ onEnter: function(args){ let soinfo = args[0]; let soname = get_soname(soinfo).readCString(); tell_init_info(soinfo, new NativeCallback((count, init_array_ptr, init_func) => { console.log(`[call_constructors] ${soname} count:${count}`); console.log(`[call_constructors] init_array_ptr:${init_array_ptr}`); console.log(`[call_constructors] init_func:${init_func} -> ${get_addr_info(init_func)}`); for (let index = 0; index < count; index++) { let init_array_func = init_array_ptr.add(Process.pointerSize * index).readPointer(); let func_info = get_addr_info(init_array_func); console.log(`[call_constructors] init_array:${index} ${init_array_func} -> ${func_info}`); } }, "void", ["int", "pointer", "pointer"])); } }); } }
|
frida_rpc
frida 提供了一种跨平台的 rpc(就是Remote Procedure Call 远程过程调用) 机制,通过 frida rpc 可以在主机和目标设备之间进行通信,并在目标设备上执行代码,简单理解就是可以不需要分析某些复杂加密,通过传入参数获取返回值,进而来实现python或易语言来调用的一系列操作,多用于爬虫。
包名附加进程
1 2 3 4 5 6 7
| import frida, sys jsCode = """ ...... """ script.exports.rpcfunc() process = frida.get_usb_device().attach('包名') script = process.create_script(jsCode) script.load() sys.stdin.read()
|
spawn方式启动
1 2 3 4 5 6 7 8 9 10
| import frida, sys jsCode = """ ...... """ script.exports.rpcfunc() device = frida.get_usb_device() pid = device.spawn(["包名"]) process = device.attach(pid) script = process.create_script(jsCode) script.load() device.resume(pid) sys.stdin.read()
|
连接非标准端口
1 2 3 4 5 6 7
| import frida, sys jsCode = """ ...... """ script.exports.rpcfunc() process = frida.get_device_manager().add_remote_device('192.168.1.22:6666').attach('包名') script = process.create_script(jsCode) script.load() sys.stdin.read()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function get_url() { let ChallengeNinth = Java.use("com.zj.wuaipojie.ui.ChallengeNinth"); ChallengeNinth["updateUI"].implementation = function (list) { let ret = this.updateUI(list); var size = list.size(); for (var i = 0; i < size; i++) { var imageEntity = Java.cast(list.get(i), Java.use('com.zj.wuaipojie.entity.ImageEntity')); console.log(imageEntity.name.value + imageEntity.cover.value); } return ret; }; }
|
1
| frida -U -n com.zj.wuaipojie -l hook.js
|
1
| frida-tools==9.2.4,uvicorn,fastapi,requests
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| from fastapi import FastAPI from fastapi.responses import JSONResponse import frida, sys import uvicorn
app = FastAPI()
@app.get("/download-images/") def download_images(): def on_message(message, data): message_type = message['type'] if message_type == 'send': print('[* message]', message['payload']) elif message_type == 'error': stack = message['stack'] print('[* error]', stack) else: print(message)
jsCode = """ function getinfo(){ var result = []; Java.perform(function(){ Java.choose("com.zj.wuaipojie.ui.ChallengeNinth",{ onMatch:function(instance){ instance.setupScrollListener(); // 调用目标方法 }, onComplete:function(){} });
Java.choose("com.zj.wuaipojie.entity.ImageEntity",{ onMatch:function(instance){ var name = instance.getName(); var cover = instance.getCover(); result.push({name: name, cover: cover}); // 收集数据 }, onComplete:function(){} }); }); return result; // 返回收集的结果 } rpc.exports = { getinfo: getinfo // 导出函数供外部调用 }; """
process = frida.get_usb_device().attach("com.zj.wuaipojie") script = process.create_script(jsCode) script.on("message", on_message) script.load() getcovers = script.exports.getinfo() print(getcovers)
return JSONResponse(content=getcovers)
if __name__ == "__main__": uvicorn.run(app, host="127.0.0.1", port=8000)
|
提示词
1 2 3 4 5 6 7 8 9 10 11
| 写一段python的requests代码,访问http: [{ "name": "霸王别姬", "cover": "https://p0.meituan.net/movie/ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg@464w_644h_1e_1c" },{ "name": "这个杀手不太冷", "cover": "https://p1.meituan.net/movie/6bea9af4524dfbd0b668eaa7e187c3df767253.jpg@464w_644h_1e_1c" },{ "name": "肖申克的救赎", "cover": "https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@464w_644h_1e_1c" }]
|