android逆向奇技淫巧二十六:基础库的hook&x音检测frida方式之一(十一)
1、万丈高楼平地起,正常人都知道高楼大厦地基的重要性!现代产业链的成熟,让产业分工越来越明细,很少有公司或团队能完整地提供产业链上每个环节的产品,很多都是基于某个厂家上游的产品继续做下游的开发,典型的如应用app开发,肯定要基于操作系统提供的api接口;同理,操作系统的开发需要基于cpu提供的汇编指令,不太可能有一家公司从cpu的制造、操作系统的开发到应用的开发大包大揽全都做(主要是管理人员没那么高超的管理能力运作几十、甚至上百万人的超大规模的团队)!站在逆向的角度,只要抓住了底层操作系统或基础库提供的接口,是不是就抓住了上层应用的执行轨迹了?
做业务应用开发,速度很重要,因为互联网是个快鱼吃慢鱼的时代:app早上线1天运营,可能会比竞争对手多圈几十万用户,牢牢树立起各种壁垒,让后来者很难追上!所以开发人员为了提高速度,大概率会用很多现成的基础库或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的线程).....
我们能在Java层面使用的各种功能,底层也都是靠这些基础库的api实现的!比如java的FileInputStream执行时,JVM肯定调用了libc库的open、read、fgetc等api来实现;又比如java的socket、outputstream等函数在jvm层面肯定调用了sockt、connect、send/sendto等api。再说直白点:libart.so肯定调用了libc.so的api,所以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函数!
(2)继续看重要的api:send和sendto,下面这个是hook的结果,能找到啥关键信息么?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
从调用栈看,send方法调用了sendto方法发数据!再往上就是java层的socketOutPutStream的socketWrite方法调用了native的send函数!所以理论上讲,去hook java层的socket类、outputStream等也能得到发送的数据!
(3)fgets函数:从文件读数据的。hook前面两个参数部分日志如下:这样是不是很明显在检测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()) } }) }
参考:
1、https://blog.csdn.net/zhangmiaoping23/article/details/109697329 多种特征检测frida