只要抓住了底层操作系统或基础库提供的接口,是不是就抓住了上层应用的执行轨迹了?
开发人员为了提高速度,大概率会用很多现成的基础库或API(避免无意义的重复造轮子);同时,由于基础库长时间被大量app使用,如果有严重的bug或性能缺陷,早就被爆出来修复了,所以直接用基础库也比自己另起炉灶单独搞一套划算得多!其中有个非常重要的基础库:libc.so! 这个库包括了大量基础的功能,分类如下:
- 字符串类的操作:strstr、strcmp、strcat、strlen(构造数据包、检测各种hook或调试等).....
- 文件类的操作:open、read、write、close、fgets(既然打开文件读写,肯定都是重要的数据要保存或读取,比如配置信息、证书、日志等).....
- 网络IO类操作:send、recv(收发数据都要通过这两个API)......
- 线程类操作:pthrea_create(可能是检测root、frida、xpose、ida的线程).....
hook这些基础库(包括java层和native层)的api时,肯定能在一定程度上追踪、还原app的各种操作!
2、(1)先看看socket函数,linux/android用这个函数建立socket描述符,x音hook的结果如下(hook脚本在文章末尾):
Entering => socket
args[0] => 0xa
args[1] => 0x2
args[2] => 0x0
called from:
0xc7bf8f43 libopenjdkjvm.so!JVM_Socket+0x33
0xc79ede46 libopenjdk.so!PlainDatagramSocketImpl_datagramSocketCreate+0x86
0x7093ff0d boot.oat!oatexec+0xe5f0d
第三个参数是protocol为0,操作系统内核会自动选择type类型对应的默认协议;第二个参数是0x2,说明socket的type是SOCK_DGRAM,也就是UDP协议啦,个人猜测:这有可能是传输音视频数据,也有可能是quic协议,需要进一步分析!还有,从调用栈的数据看,果然是上层的jvm在调用native层的socket函数!
x音的X-Khronos、X-Gorgon都能看到(在这里打个调用栈,是不是就能看到关键的生成代码了?)! 从接口的名称看,貌似是下载图片的接口!
Entering => send
args[0] => 0xf1
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
9aeea748 47 45 54 20 2f 74 6f 73 2d 63 6e 2d 70 2d 30 30 GET /tos-cn-p-00
9aeea758 31 35 2f 30 34 62 32 65 61 66 62 30 30 39 31 34 15/04b2eafb00914
9aeea768 65 61 63 61 63 39 35 32 32 65 64 63 63 34 63 37 eacac9522edcc4c7
9aeea778 64 65 63 5f 31 36 34 39 35 36 34 38 33 34 7e 74 dec_1649564834~t
9aeea788 70 6c 76 2d 6e 6f 6f 70 2e 69 6d 61 67 65 3f 78 plv-noop.image?x
9aeea798 2d 65 78 70 69 72 65 73 3d 31 36 34 39 35 37 39 -expires=1649579
9aeea7a8 37 30 37 26 78 2d 73 69 67 6e 61 74 75 72 65 3d 707&x-signature=
9aeea7b8 68 49 4e 64 79 31 5a 56 48 76 71 68 55 35 4c 4c hINdy1ZVHvqhU5LL
9aeea7c8 48 72 4b 67 43 6a 53 6d 72 42 59 25 33 44 20 48 HrKgCjSmrBY%3D H
9aeea7d8 54 54 50 2f 31 2e 31 0d 0a 41 63 63 65 70 74 2d TTP/1.1..Accept-
9aeea7e8 45 6e 63 6f 64 69 6e 67 3a 20 69 64 65 6e 74 69 Encoding: identi
9aeea7f8 74 79 0d 0a 52 61 6e 67 65 3a 20 62 79 74 65 73 ty..Range: bytes
9aeea808 3d 30 2d 0d 0a 58 2d 4b 68 72 6f 6e 6f 73 3a 20 =0-..X-Khronos:
9aeea818 31 36 34 39 35 37 36 30 31 30 0d 0a 58 2d 47 6f 1649576010..X-Go
9aeea828 72 67 6f 6e 3a 20 30 34 30 34 38 30 37 39 30 30 rgon: 0404807900
9aeea838 30 30 33 61 39 32 34 66 64 31 36 63 61 35 62 33 003a924fd16ca5b3
9aeea848 34 38 38 66 34 33 65 30 64 62 36 38 38 39 61 38 488f43e0db6889a8
9aeea858 31 66 61 39 30 63 37 35 38 34 0d 0a 48 6f 73 74 1fa90c7584..Host
9aeea868 3a 20 70 33 2d 73 69 67 6e 2e 64 6f 75 79 69 6e : p3-sign.douyin
9aeea878 70 69 63 2e 63 6f 6d 0d 0a 43 6f 6e 6e 65 63 74 pic.com..Connect
9aeea888 69 6f 6e 3a 20 4b 65 65 70 2d 41 6c 69 76 65 0d ion: Keep-Alive.
9aeea898 0a 55 73 65 72 2d 41 67 65 6e 74 3a 20 6f 6b 68 .User-Agent: okh
9aeea8a8 74 74 70 2f 33 2e 31 30 2e 30 2e 31 0d 0a 0d 0a ttp/3.10.0.1....
args[2] => 0x170
called from:
0xc79d8c42 libopenjdk.so!NET_Send+0x62
0xc79f2a83 libopenjdk.so!SocketOutputStream_socketWrite0+0x133
0x7094a7ed boot.oat!oatexec+0xf07ed
Entering => sendto
args[0] => 0xf1
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
9aeea748 47 45 54 20 2f 74 6f 73 2d 63 6e 2d 70 2d 30 30 GET /tos-cn-p-00
9aeea758 31 35 2f 30 34 62 32 65 61 66 62 30 30 39 31 34 15/04b2eafb00914
9aeea768 65 61 63 61 63 39 35 32 32 65 64 63 63 34 63 37 eacac9522edcc4c7
9aeea778 64 65 63 5f 31 36 34 39 35 36 34 38 33 34 7e 74 dec_1649564834~t
9aeea788 70 6c 76 2d 6e 6f 6f 70 2e 69 6d 61 67 65 3f 78 plv-noop.image?x
9aeea798 2d 65 78 70 69 72 65 73 3d 31 36 34 39 35 37 39 -expires=1649579
9aeea7a8 37 30 37 26 78 2d 73 69 67 6e 61 74 75 72 65 3d 707&x-signature=
9aeea7b8 68 49 4e 64 79 31 5a 56 48 76 71 68 55 35 4c 4c hINdy1ZVHvqhU5LL
9aeea7c8 48 72 4b 67 43 6a 53 6d 72 42 59 25 33 44 20 48 HrKgCjSmrBY%3D H
9aeea7d8 54 54 50 2f 31 2e 31 0d 0a 41 63 63 65 70 74 2d TTP/1.1..Accept-
9aeea7e8 45 6e 63 6f 64 69 6e 67 3a 20 69 64 65 6e 74 69 Encoding: identi
9aeea7f8 74 79 0d 0a 52 61 6e 67 65 3a 20 62 79 74 65 73 ty..Range: bytes
9aeea808 3d 30 2d 0d 0a 58 2d 4b 68 72 6f 6e 6f 73 3a 20 =0-..X-Khronos:
9aeea818 31 36 34 39 35 37 36 30 31 30 0d 0a 58 2d 47 6f 1649576010..X-Go
9aeea828 72 67 6f 6e 3a 20 30 34 30 34 38 30 37 39 30 30 rgon: 0404807900
9aeea838 30 30 33 61 39 32 34 66 64 31 36 63 61 35 62 33 003a924fd16ca5b3
9aeea848 34 38 38 66 34 33 65 30 64 62 36 38 38 39 61 38 488f43e0db6889a8
9aeea858 31 66 61 39 30 63 37 35 38 34 0d 0a 48 6f 73 74 1fa90c7584..Host
9aeea868 3a 20 70 33 2d 73 69 67 6e 2e 64 6f 75 79 69 6e : p3-sign.douyin
9aeea878 70 69 63 2e 63 6f 6d 0d 0a 43 6f 6e 6e 65 63 74 pic.com..Connect
9aeea888 69 6f 6e 3a 20 4b 65 65 70 2d 41 6c 69 76 65 0d ion: Keep-Alive.
9aeea898 0a 55 73 65 72 2d 41 67 65 6e 74 3a 20 6f 6b 68 .User-Agent: okh
9aeea8a8 74 74 70 2f 33 2e 31 30 2e 30 2e 31 0d 0a 0d 0a ttp/3.10.0.1....
args[2] => 0x170
called from:
0xc4d4c6d7 libc.so!send+0x47
0xc79d8c42 libopenjdk.so!NET_Send+0x62
0xc79f2a83 libopenjdk.so!SocketOutputStream_socketWrite0+0x133
0x7094a7ed boot.oat!oatexec+0xf07ed
exit => sendto
再往上就是java层的socketOutPutStream的socketWrite方法调用了native的send函数!所以理论上讲,去hook java层的socket类、outputStream等也能得到发送的数据!
很明显在检测frida了?
Entering => fgets
args[0] => abda5000-abfe8000 rw-p 00000000 00:00 0 [anon:.bss]
args[1] => 0x200
retval is => abfe8000-ad635000 r-xp 00000000 08:12 917529 /data/local/tmp/re.frida.server/frida-agent-32.so
exit => fgets
Entering => fgets
args[0] => abfe8000-ad635000 r-xp 00000000 08:12 917529 /data/local/tmp/re.frida.server/frida-agent-32.so
args[1] => 0x200
retval is => ad635000-ad681000 r--p 0164c000 08:12 917529 /data/local/tmp/re.frida.server/frida-agent-32.so
exit => fgets
Entering => fgets
args[0] => ad635000-ad681000 r--p 0164c000 08:12 917529 /data/local/tmp/re.frida.server/frida-agent-32.so
args[1] => 0x200
retval is => ad681000-ad68f000 rw-p 01698000 08:12 917529 /data/local/tmp/re.frida.server/frida-agent-32.so
exit => fgets
Entering => fgets
args[0] => ad681000-ad68f000 rw-p 01698000 08:12 917529 /data/local/tmp/re.frida.server/frida-agent-32.so
args[1] => 0x200
retval is => ad68f000-ad6c2000 rw-p 00000000 00:00 0 [anon:.bss]
个人猜测检测的代码可能是这样写的:
char line[0x200];
FILE* fp;
fp = fopen("/proc/self/maps", "r");
if (fp) {
while (fgets(line, 0x200, fp)) {
if (strstr(line, "frida")) {
/* Evil library is loaded. Do something… 检测frida的代码逻辑*/
}
}
fclose(fp);
} else {
/* Error opening /proc/self/maps. If this happens, something is off. */
}
}
顺着这个思路,也可以hook strstr、strcmp等常见的函数看看有没有检测的逻辑!
(4)hook的核心js代码:整体的思路很简单,就是遍历modules,找到libc.so;然后进一步找到遍历库的导出表,找到关键的库函数去hook!
function traceNativeExport(){
var modules = Process.enumerateModules();
for(var i = 0;i<modules.length;i++){
var module = modules[i];
//只hook libc.so
if(module.name.indexOf("libc.so")<0){
continue;
}
var exports = module.enumerateExports();
for(var j = 0;j<exports.length;j++){
//console.log("module name is =>",module.name," symbol name is =>",exports[j].name)
//var path = "/sdcard/Download/so/"+module.name+".txt"
// var path = "/data/data/com.ss.aweme/cache/"+module.name+".txt"
// writeSomething(path,"type: "+exports[j].type+" function name :"+exports[j].name+" address : "+exports[j].address+" offset => 0x"+ ( exports[j].address.sub(modules[i].base) )+"\n")
// if(exports[j].name.indexOf("strto")>=0)continue;
// if(exports[j].name.indexOf("strco")>=0)continue;
// if(exports[j].name.indexOf("_l")>=0)continue;
// if(exports[j].name.indexOf("pthread")>=0)continue;
//int socket(int domain, int type, int protocol);
/*if(exports[j].name.indexOf("socket")>=0){
attach(exports[j].name,exports[j].address);
}*/
/*if(exports[j].name.indexOf("pthread_create")>=0){
attach(exports[j].name,exports[j].address);
}*/
//int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen)
/*if(exports[j].name.indexOf("connect")>=0){
attach(exports[j].name,exports[j].address);
}*/
// if(exports[j].name.indexOf("read")>=0){
// attach(exports[j].name,exports[j].address);
// }
// if(exports[j].name.indexOf("write")>=0){
// attach(exports[j].name,exports[j].address);
// }
//ssize_t send(int sockfd, const void *buf, size_t len, int flags);
/*if(exports[j].name.indexOf("send")>=0){
attach(exports[j].name,exports[j].address);
}
/*if(exports[j].name.indexOf("strstr")>=0){
attach(exports[j].name,exports[j].address);
}*/
/*if(exports[j].name.indexOf("strcmp")>=0){
attach(exports[j].name,exports[j].address);
}*/
if(exports[j].name.indexOf("fgets")>=0){
attach(exports[j].name,exports[j].address);
}
// if(exports[j].name.indexOf("recv")>=0){
// attach(exports[j].name,exports[j].address);
// }
}
}
}
function attach(name,address){
console.log("attaching ",name);
Interceptor.attach(address,{
onEnter:function(args){
console.log("Entering => " ,name)
console.log("args[0] => ",args[0].readCString())
console.log("args[1] => ",args[1])
/*if(args[0].readCString().indexOf("frida")>=0
||args[0].readCString().indexOf("xpose")>=0
||args[1].readCString().indexOf("xpose")>=0
||args[1].readCString().indexOf("frida")>=0){
console.log("Entering => " ,name)
console.log("args[0] => ",args[0].readCString())
console.log("args[1] => ",args[1].readCString())
console.log('\n called from:\n' +Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');
}*/
/*console.log("args[0] => ",args[0].readCString())
console.log("args[1] => ",args[1].readCString())*/
/*console.log( hexdump(args[0],{
offset: 0,
length: parseInt(args[1]),
header: true,
ansi: true
}))*/
//console.log("args[2] => ",args[2])
//console.log('\n called from:\n' +Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');
},onLeave:function(retval){
console.log("retval is => ",retval.readCString())
console.log("\n exit => ",name)
// console.log("retval is => ",retval.readCString())
}
})
}