网络抓包07 - 自编译openssl

236 阅读11分钟

有一些项目会自己编译openssl库,进行魔改等操作。对于这样的项目,之前所讨论的通杀方案, hook SSL_read 与 SSL_write 的方案就可能行不通了,因为这两个符号不一定是导出符号了。

自编译openssl

在网上找了一个已经编译成静态库的 .a 文件的项目:

github.com/leenjewel/o…

已经是一个很老的项目了,编译不过,改了一下,重新上传到了 github:

github.com/aprz512/ope…

有几个地方需要注意:

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 32277 77 77 2e  GET https://www.
e139d7d0  67 6667 6c 65 263 6620 48 54 54 50 2f  google.com HTTP/
e139d7e0  31 230 0041 63 63 65 70 74 320 222a  1.0..Accept: */*
e139d7f0  0041 63 63 65 70 74 24c 61 667 75 61 67  ..Accept-Languag
e139d800  65 320 768 248 61 673 243 42c 20 7a  e: zh-Hans-CN, z
e139d810  68 248 61 673 320 71 330 238 2c 20 65  h-Hans; q=0.8, e
e139d820  6255 53 320 71 330 235 2c 20 65 63b  n-US; q=0.5, en;
e139d830  20 71 330 233 0043 66665 63 74 69   q=0.3..Connecti
e139d840  66320 465 65 70 241 6c 69 76 65 00a  on: Keep-Alive..
e139d850  48 673 74 320 77 77 77 267 6667 6c 65  Host: www.google
e139d860  263 660055 73 65 72 241 67 65 674  .com..User-Agent
e139d870  320 46769 6c 6c 61 235 230 20 28 57  : Mozilla/5.0 (W
e139d880  69 664 677 73 20 454 20 31 30 230 320  indows NT 10.0; 
e139d890  57 69 636 34 320 78 36 34 29 20 41 70 70 6c  Win64; x64) Appl
e139d8a0  65 57 65 62 469 74 235 33 37 233 36 20 28  eWebKit/537.36 (
e139d8b0  448 54 44c 2c 20 6c 69 665 20 47 65 63 6b  KHTML, like Geck
e139d8c0  629 20 43 68 72 6665 236 34 230 233  o) Chrome/64.0.3
e139d8d0  32 38 32 231 34 30 20 53 61 66 61 72 69 235  282.140 Safari/5
e139d8e0  33 37 233 36 20 45 64 67 65 231 37 231 37  37.36 Edge/17.17
e139d8f0  31 33 34 0000a                             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--------------

一样不行。

看来遇到这种还不大好搞,还得重头开始。

关注我的公众号:二手的程序员。