网络抓包07 - 自编译openssl
有一些项目会自己编译openssl库,进行魔改等操作。对于这样的项目,之前所讨论的通杀方案, hook SSL_read 与 SSL_write 的方案就可能行不通了,因为这两个符号不一定是导出符号了。
自编译openssl
在网上找了一个已经编译成静态库的 .a 文件的项目:
https://github.com/leenjewel/openssl_for_ios_and_android
已经是一个很老的项目了,编译不过,改了一下,重新上传到了 github:
https://github.com/aprz512/openssl_for_android
有几个地方需要注意:
abiFilters "x86_64", "x86", "armeabi-v7a"
原项目中的 .a 文件很老了,编 arm64 的时候会报错:
relocation R_AARCH64_PREL64 against symbol `OPENSSL_armcap_P' which may bind externally can not be used when making a shared object; recompile with -fPIC
需要重新编译 .a 文件,懒得编译的,就干掉了 arm64 的选项。反正也是学习,用 arm32 的也够了。需要研究 arm64 的,可以找最新的已经编译好的文件,替换报错的文件。
为了模拟符号魔改的情况,需要将 so 中的符号都隐藏起来:
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden")set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--exclude-libs,ALL")#使所有静态库中的符号都不被导出
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--exclude-libs,libabc.a")#使 libabc.a 的符号都不被导出
这样编译出来的 so 就看不到符号了。
IDA反编译
将编译出来的 so,使用 IDA 打开,看 exports 窗口:
发现只有一项。
这个时候使用 frida 通杀脚本肯定是不管用了。
所以我们需要使用堆栈回溯的方式来找到 SSL_read 与 SSL_write 函数。
SSL_read 最终会调用到 libc.so 中的 read 函数,我们 hook 这个方法,打印堆栈,看看有哪个堆栈是有 libnative_lib.so 的。
frida脚本
以 write 方法为例:
Interceptor.attach(write_addr, {onEnter: function (args) {this.arg0 = args[0];this.arg1 = args[1];this.arg2 = args[2];this.socketinfo = getsocketdetail(this.arg0.toInt32());this.flag = false;if (this.socketinfo.indexOf("tcp") >= 0) {this.flag = true;}if (this.flag) {printNativeStack(this.context, Process.getCurrentThreadId() + "write");var size = ptr(this.arg2).toInt32();if (size > 0) {console.log(Process.getCurrentThreadId() + "---libc.so->write:" + hexdump(this.arg1, {length: size}));}}}, onLeave(retval) {LogPrint("leave libc.so->write");}});
frida 为 Sock 提供了很多的 api,我们可以直接使用。
write 方法的第一次参数是一个 fd:
ssize_t write(int fd, const void buf[.count], size_t count);
使用:
Socket.type(fd);
Socket.peerAddress(fd);
Socket.localAddress(fd);
这些 api,可以获取 socket 的一些信息,利用这些信息,我们可以过滤一些不关心的方法调用。
堆栈分析
看其中的一个堆栈:
[00:55:36:420]->threadid:17931-------------start:17931read--------------
[00:55:36:420]->threadid:17931--0xd45be9f8 libnative-lib.so!0x15f9f8
0xd45bcf1c libnative-lib.so!0x15df1c
0xd45bbff4 libnative-lib.so!0x15cff4
0xd45aa5fc libnative-lib.so!0x14b5fc
0xd45bbf0c libnative-lib.so!0x15cf0c
0xd456e19c libnative-lib.so!0x10f19c
0xd45707d8 libnative-lib.so!0x1117d8
0xd45aa234 libnative-lib.so!0x14b234
0xf3f8324a libc.so!malloc+0x15
0xd456f604 libnative-lib.so!0x110604
0xd459ce2c libnative-lib.so!0x13de2c
0xd4593590 libnative-lib.so!0x134590
0xd4658ddc libnative-lib.so!0x1f9ddc
0xd4554300 libnative-lib.so!0xf5300
0xf3fe80ec libc.so!__vfprintf+0x1917
0xf312dc3a libart.so!artQuickToInterpreterBridge+0x409
[00:55:36:420]->threadid:17931-------------end:17931read--------------
看对应位置的 IDA 反编译代码:
libnative-lib.so!0x15f9f8 --> BL read
libnative-lib.so!0x15df1c --> BLX R3
看起来堆栈没啥问题。
后续就可以一点点尝试看看哪个方法是 SSL_write 了,结合源码分析与对比反编译代码中出现的字符串,最后得到 SSL_write 对应的方法。
由于这个是学习,所以我们可以再编译一个不隐藏符号的 so,对比看一下:
[01:27:19:672]->threadid:19325-------------start:19325read--------------
[01:27:19:672]->threadid:19325--0xd42ac9f8 libnative-lib.so!0x1a79f8
0xd42aaf1c libnative-lib.so!bread_conv+0x20
0xd42a9ff4 libnative-lib.so!0x1a4ff4
0xd42a9f0c libnative-lib.so!BIO_read+0x20
0xd425c19c libnative-lib.so!ssl3_read_n+0x23c
0xd425e568 libnative-lib.so!ssl3_get_record+0x8c
0xf3f8324a libc.so!malloc+0x15
0xd425d604 libnative-lib.so!ssl3_read_bytes+0x1ac
0xd4263c10 libnative-lib.so!0x15ec10
0xd4263b8c libnative-lib.so!ssl3_read+0x18
0xd426caec libnative-lib.so!SSL_read+0x1c
0xd4245a10 libnative-lib.so!0x140a10
0xd424ea08 libnative-lib.so!0x149a08
0xd4248430 libnative-lib.so!0x143430
0xd424981c libnative-lib.so!nghttp2_session_send+0x84
0xd424e1c0 libnative-lib.so!nghttp2_session_resume_data+0xc
[01:27:19:672]->threadid:19325-------------end:19325read--------------
发现堆栈并不是完全一样。后来发现是 curl 的原因,不会稳定触发 SSL_read,比较奇怪。
需要使用 https 的 demo,但是编译比较麻烦,可以自己调用 SSL_read,然后想办法找到这个函数,也是一样的。
发送https请求
stringstream stream;stream << "GET https://" << host << " HTTP/1.0\r\n";stream << "Accept: */*\r\n";// stream << "Accept-Encoding: gzip, deflate,// br\r\n";//不要编码,否则还得多一个解码的步骤stream << "Accept-Language: zh-Hans-CN, zh-Hans; q=0.8, en-US; q=0.5, en; ""q=0.3\r\n";stream << "Connection: Keep-Alive\r\n";stream << "Host: " << host << "\r\n";stream << "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ""AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 ""Safari/537.36 Edge/17.17134\r\n";stream << "\r\n";string s = stream.str();const char *sendData = s.c_str();ret = SSL_write(ssl, sendData, strlen(sendData));
完整代码,放到工程里面了。
我们 hook SSL_read,得到输出:
---29213---libssl.so->SSL_write: 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
e139d7c0 47 45 54 20 68 74 74 70 73 3a 2f 2f 77 77 77 2e GET https://www.
e139d7d0 67 6f 6f 67 6c 65 2e 63 6f 6d 20 48 54 54 50 2f google.com HTTP/
e139d7e0 31 2e 30 0d 0a 41 63 63 65 70 74 3a 20 2a 2f 2a 1.0..Accept: */*
e139d7f0 0d 0a 41 63 63 65 70 74 2d 4c 61 6e 67 75 61 67 ..Accept-Languag
e139d800 65 3a 20 7a 68 2d 48 61 6e 73 2d 43 4e 2c 20 7a e: zh-Hans-CN, z
e139d810 68 2d 48 61 6e 73 3b 20 71 3d 30 2e 38 2c 20 65 h-Hans; q=0.8, e
e139d820 6e 2d 55 53 3b 20 71 3d 30 2e 35 2c 20 65 6e 3b n-US; q=0.5, en;
e139d830 20 71 3d 30 2e 33 0d 0a 43 6f 6e 6e 65 63 74 69 q=0.3..Connecti
e139d840 6f 6e 3a 20 4b 65 65 70 2d 41 6c 69 76 65 0d 0a on: Keep-Alive..
e139d850 48 6f 73 74 3a 20 77 77 77 2e 67 6f 6f 67 6c 65 Host: www.google
e139d860 2e 63 6f 6d 0d 0a 55 73 65 72 2d 41 67 65 6e 74 .com..User-Agent
e139d870 3a 20 4d 6f 7a 69 6c 6c 61 2f 35 2e 30 20 28 57 : Mozilla/5.0 (W
e139d880 69 6e 64 6f 77 73 20 4e 54 20 31 30 2e 30 3b 20 indows NT 10.0;
e139d890 57 69 6e 36 34 3b 20 78 36 34 29 20 41 70 70 6c Win64; x64) Appl
e139d8a0 65 57 65 62 4b 69 74 2f 35 33 37 2e 33 36 20 28 eWebKit/537.36 (
e139d8b0 4b 48 54 4d 4c 2c 20 6c 69 6b 65 20 47 65 63 6b KHTML, like Geck
e139d8c0 6f 29 20 43 68 72 6f 6d 65 2f 36 34 2e 30 2e 33 o) Chrome/64.0.3
e139d8d0 32 38 32 2e 31 34 30 20 53 61 66 61 72 69 2f 35 282.140 Safari/5
e139d8e0 33 37 2e 33 36 20 45 64 67 65 2f 31 37 2e 31 37 37.36 Edge/17.17
e139d8f0 31 33 34 0d 0a 0d 0a 134....
对应的堆栈如下:
[21:59:59:409]->threadid:29213-------------start:29213SSL_write--------------
[21:59:59:409]->threadid:29213--0xd43fc777 libnative-lib.so!main+0x236
0xd43fc076 libnative-lib.so!Java_com_example_test_1curl_1with_1ssl_1and_1http2_1android_MainActivity_stringFromJNI+0x21
0xf2df251a libart.so!art_quick_generic_jni_trampoline+0x29
0xf2e35f9c libart.so!_ZN3art10ClassTable6LookupEPKcj+0x107
0xf2dedbc6 libart.so!art_quick_invoke_stub_internal+0x45
0xf3143fea libart.so!art_quick_invoke_stub+0xfd
0xf2e35f9c libart.so!_ZN3art10ClassTable6LookupEPKcj+0x107
0xf2df5fba libart.so!_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+0xb1
0xf2e0d282 libart.so!_ZN3art11ClassLinker11LookupClassEPNS_6ThreadEPKcjNS_6ObjPtrINS_6mirror11ClassLoaderEEE+0x75
0xf2f269fc libart.so!_ZN3art11interpreter34ArtInterpreterToCompiledCodeBridgeEPNS_6ThreadEPNS_9ArtMethodEPNS_11ShadowFrameEtPNS_6JValueE+0x11b
0xf2e0dc4c libart.so!_ZN3art11ClassLinker9FindClassEPNS_6ThreadEPKcNS_6HandleINS_6mirror11ClassLoaderEEE+0x4b
0xf2f2233e libart.so!_ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE+0x309
0xf31392bc libart.so!MterpInvokeVirtual+0x22f
0xf2e0097e libart.so!_ZN3art11ClassLinker13ResolveMethodILNS0_11ResolveModeE0EEEPNS_9ArtMethodEjNS_6HandleINS_6mirror8DexCacheEEENS5_INS6_11ClassLoaderEEES4_NS_10InvokeTypeE+0xe1
0xf2de8818 libart.so!mterp_op_invoke_virtual+0x18
0xf313a9ac libart.so!MterpInvokeInterface+0x59b
[21:59:59:410]->threadid:29213-------------end:29213SSL_write--------------
调用链没问题。
再看 libc.so 的 write 方法的堆栈:
[21:59:59:499]->threadid:29213-------------start:29213write--------------
[21:59:59:499]->threadid:29213--0xd44b0b64 libnative-lib.so!0x1aab64
0xd44af084 libnative-lib.so!bwrite_conv+0x20
0xd44ae3c4 libnative-lib.so!0x1a83c4
0xd44ae2d4 libnative-lib.so!BIO_write+0x20
0xd4460888 libnative-lib.so!ssl3_write_pending+0xac
0xd4460a08 libnative-lib.so!do_ssl3_write+0x8c
0xd455a414 libnative-lib.so!0x254414
0xcca27d68 frida-agent-32.so!0x4cbd68
0xcca16e0c frida-agent-32.so!0x4bae0c
0xccb14f52 frida-agent-32.so!0x5b8f52
0xcca9a8ae frida-agent-32.so!0x53e8ae
0xcca9a610 frida-agent-32.so!0x53e610
0xccab94a0 frida-agent-32.so!0x55d4a0
0xcca66718 frida-agent-32.so!0x50a718
0xeb06fb24
0xd44606ec libnative-lib.so!ssl3_write_bytes+0x2ac
[21:59:59:499]->threadid:29213-------------end:29213write--------------
这里看不到 SSL_write,看来堆栈不是很准确。
换个堆栈模式:
[22:36:03:983]->threadid:29213-------------start:29213write--------------
[22:36:03:983]->threadid:29213--0xd44b0b64 libnative-lib.so!0x1aab64
0xd44b0b64 libnative-lib.so!0x1aab64
[22:36:03:983]->threadid:29213-------------end:29213write--------------
一样不行。
看来遇到这种还不大好搞,还得重头开始。
关注我的公众号:二手的程序员。