网络抓包05 - r0capture分析

项目历史

https://github.com/r0ysue/r0capture

是基于谷歌的项目而来的

https://github.com/google/ssl_logger

可以先看看这个项目,比较纯粹,就一个python文件。

源码分析

r0capture.py 分析

if __name__ == "__main__":
    show_banner()

    class ArgParser(argparse.ArgumentParser):

        ...

    parser = ArgParser()

    args = parser.add_argument_group("Arguments")
    args.add_argument("-pcap", '-p', metavar="<path>", required=False,
                      help="Name of PCAP file to write")
    ...

    parsed = parser.parse_args()
    
    ssl_log(
        int(parsed.process) if parsed.process.isdigit() else parsed.process,
        parsed.pcap,
        parsed.host,
        parsed.verbose,
        isUsb=parsed.isUsb,
        isSpawn=parsed.isSpawn,
        ssllib=parsed.ssl,
        wait=parsed.wait
    )

使用 ArgumentParser 来做命令行参数的解析。最后调用 ssl_log 函数。

ssl_log 分下面几步:

  • 连接设备

    if isUsb:
        try:
            device = frida.get_usb_device()
        except:
            device = frida.get_remote_device()
    else:
        if host:
            manager = frida.get_device_manager()
            device = manager.add_remote_device(host)
        else:
            device = frida.get_local_device()

    if isSpawn:
        pid = device.spawn([process])
        time.sleep(1)
        session = device.attach(pid)
        time.sleep(1)
        device.resume(pid)
    else:
        print("attach")
        session = device.attach(process)
    if wait > 0:
        print(f"wait for {wait} seconds")
        time.sleep(wait)
  • 如果命令参数有 pcap,就创建 pcap 文件,将hook到的数据储存进去。这里只是先写了一个文件头。pcap 的格式可看:

    https://blog.csdn.net/in7deforever/article/details/6460595

    if pcap:
        pcap_file = open(pcap, "wb", 0)
        for writes in (
                ("=I", 0xa1b2c3d4),  # Magic number
                ("=H", 2),  # Major version number
                ("=H", 4),  # Minor version number
                ("=i", time.timezone),  # GMT to local correction
                ("=I", 0),  # Accuracy of timestamps
                ("=I", 65535),  # Max length of captured packets
                ("=I", 228)):  # Data link type (LINKTYPE_IPV4)
            pcap_file.write(struct.pack(writes[0], writes[1]))
  • 加载脚本,调用 rpc 方法

    with open(Path(__file__).resolve().parent.joinpath("./script.js"), encoding="utf-8") as f:
        _FRIDA_SCRIPT = f.read()
        # _FRIDA_SCRIPT = session.create_script(content)
        # print(_FRIDA_SCRIPT)
    script = session.create_script(_FRIDA_SCRIPT)
    script.on("message", on_message)
    script.load()

    if ssllib != "":
        script.exports.setssllib(ssllib)
  • 监听中断信号

    signal.signal(signal.SIGINT, stoplog)
    signal.signal(signal.SIGTERM, stoplog)
    sys.stdin.read()

script.js分析

加载该脚本的时候,一些方法就开始执行了:

initializeGlobals()

它还有另一个触发入口。

上面分析过,从 py 文件 rpc 过来的方法叫 setssllib,当然这个方法我们也可以手动调用。

rpc.exports = {
  setssllib: function (name) {
    console.log("setSSLLib => " + name);
    libname = name;
    initializeGlobals();
    return;
  }
};

该方法主要是先初始化一些环境变量,比如 so 的名字以及符号地址。

这里使用了一个 frida 的 api,ApiResolver。

  var resolver = new ApiResolver("module");
  var exps = [
    [Process.platform == "darwin" ? "*libboringssl*" : "*libssl*", ["SSL_read", "SSL_write", "SSL_get_fd", "SSL_get_session", "SSL_SESSION_get_id"]], // for ios and Android
    [Process.platform == "darwin" ? "*libsystem*" : "*libc*", ["getpeername", "getsockname", "ntohs", "ntohl"]]
  ];

还兼容了 ios。ApiResolver 会自动搜索符合匹配规则的符号地址,比如:

const resolver = new ApiResolver('module');
const matches = resolver.enumerateMatches('exports:*!open*');
const first = matches[0];

上面的搜索规则是:在导出符号表里面搜索包含 !open 字符串的符号,就会搜索到这样的一个结果:

name: '/usr/lib/libSystem.B.dylib!opendir$INODE64',
address: ptr('0x7fff870135c9')

回到源码,看它的搜索规则:

      var matches = resolver.enumerateMatchesSync("exports:" + lib + "!" + name);

至于为啥要这样写,找个系统 ssl 库,拖到 ida 里面看一下就知道了。而且,上一篇文章我们也分析过调用链了。

搜索完成之后,保存这些符号的地址:

  if (addresses["SSL_get_fd"] == 0) {
    SSL_get_fd = return_zero;
  } else {
    SSL_get_fd = new NativeFunction(addresses["SSL_get_fd"], "int", ["pointer"]);
  }
  SSL_get_session = new NativeFunction(addresses["SSL_get_session"], "pointer", ["pointer"]);
  SSL_SESSION_get_id = new NativeFunction(addresses["SSL_SESSION_get_id"], "pointer", ["pointer", "pointer"]);
  getpeername = new NativeFunction(addresses["getpeername"], "int", ["int", "pointer", "pointer"]);
  getsockname = new NativeFunction(addresses["getsockname"], "int", ["int", "pointer", "pointer"]);
  ntohs = new NativeFunction(addresses["ntohs"], "uint16", ["uint16"]);
  ntohl = new NativeFunction(addresses["ntohl"], "uint32", ["uint32"]);

有了这些符号地址之后,我们就可以做Hook操作了,拿 SSL_read 来举例:

Interceptor.attach(addresses["SSL_read"],
  {
    onEnter: function (args) {
      var message = getPortsAndAddresses(SSL_get_fd(args[0]), true);
      message["ssl_session_id"] = getSslSessionId(args[0]);
      message["function"] = "SSL_read";
      message["stack"] = SSLstackread;
      this.message = message;
      this.buf = args[1];
    },
    onLeave: function (retval) {
      retval |= 0; // Cast retval to 32-bit integer.
      if (retval <= 0) {
        return;
      }
      send(this.message, Memory.readByteArray(this.buf, retval));
    }
  });

在 SSL_read 函数返回之前,将数据发送到 py 脚本。

其中 ssl_session_id 等值的获取API需要阅读系统源码,就不展开了。

r0capture.py 分析

再回到 py 文件的 on_message 方法。

支持两种模式,一种是 verbose,一种是 pcap。verbose 就是直接将数据打印出来。pcap 是将数据储存成文件。我们分析 pcap 模式。

  • 随机 sessionId,我们并不关心数据传输会话序号

        if ssl_session_id not in ssl_sessions:
            ssl_sessions[ssl_session_id] = (random.randint(0, 0xFFFFFFFF),
                                            random.randint(0, 0xFFFFFFFF))
        client_sent, server_sent = ssl_sessions[ssl_session_id]
  • 先写数据包的头部,再写数据

        for writes in (
                # PCAP record (packet) header
                ("=I", int(t)),  # Timestamp seconds
                ("=I", int((t * 1000000) % 1000000)),  # Timestamp microseconds
                ("=I", 40 + len(data)),  # Number of octets saved
                ("=i", 40 + len(data)),  # Actual length of packet
                # IPv4 header
                (">B", 0x45),  # Version and Header Length
                (">B", 0),  # Type of Service
                (">H", 40 + len(data)),  # Total Length
                (">H", 0),  # Identification
                (">H", 0x4000),  # Flags and Fragment Offset
                (">B", 0xFF),  # Time to Live
                (">B", 6),  # Protocol
                (">H", 0),  # Header Checksum
                (">I", src_addr),  # Source Address
                (">I", dst_addr),  # Destination Address
                # TCP header
                (">H", src_port),  # Source Port
                (">H", dst_port),  # Destination Port
                (">I", seq),  # Sequence Number
                (">I", ack),  # Acknowledgment Number
                (">H", 0x5018),  # Header Length and Flags
                (">H", 0xFFFF),  # Window Size
                (">H", 0),  # Checksum
                (">H", 0)):  # Urgent Pointer
            pcap_file.write(struct.pack(writes[0], writes[1]))
        pcap_file.write(data)

欢迎关注我的微信公众号:二手的程序员。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值