测试 APP 抓不到数据包该怎么办

2020-09-23 10:38:41 浏览数 (1)

最近几次测试 APP 时,遇到过几次非 http/https 通信的情况,burp、fiddler 等 http 代理工具都无法正常抓到包,经过分析发现 app 是通过 socket 通信的,所以写出来记录下。

socket 与 websocket

先来区别下 socket 与 websocket,因为我们在使用 burpsuite 和 fiddler 时,发现 burp 和 fiddler 都是可以抓 websocket 的,所以有必要先区别一下,从本质上来说二者关系并不大,甚至说没啥关系,盗用一张图来说明下二者关系,读者可自行百度、谷歌检索二者关系。

socket 抓包思路

为了方便理解,我们自己可以实现一个简单的通过 socket 通信的 APP 和与之其对应的 Server,实现一个简单功能,客户端 APP 发送 socket 消息,模拟平时项目中 APP 调用 socket 相关接口通信,同时接收服务端下发的 socket 消息,客户端 APP 运行如下所示:

服务端通过 ServerSocket 构造器实现 socket 监听绑定即可,运行如下所示:

以上,就简单实现了一个通过 socket 通信的 c/s,通过这种方式发送的数据包,burp 和 fiddler 之类的代理工具是无法抓到的,因为他们本来就属于 http/https/websocket 代理工具,对 socket 是无能为力的,所以我们需要换些思路。

tcpdump wireshark

这种方式抓包非常通用,不光针对 socket 方式,http/https 等等也是可以的,因为这些两种抓包工具都是直接对流经网卡的数据包进行捕获,不存在区别信息传递使用什么协议,可以通过 tcpdump 将数据包保存成 pcap 格式,然后用 wireshark 打开进行分析,来看下用 tcpdump 抓到的数据包:

客户端发送的数据包

服务端接收的数据包

tcpdump 显示数据包格式不是很友好,导入到 wireshark 或者可用 wireshark 直接抓包,分析起来就比较容易了,可以看到数据传输是通过 socket 传输的:

hook 方式抓包

上述方法虽然抓包很好,但是对于渗透测试来说,我们不仅仅要看到数据包内容,更重要的是还能修改数据包,所以这里还可以使用 hook 方式抓包,在实现 socket 通信的过程,客户端(基于 android6.0 系统)发送消息需要会调用java.io.OutputStream.write() 方法,也有可能是 java.io.PrintWriter.write() 方法,这里以java.io.OutputStream.write 方法为例,这个方法有三个重载,分别是 write(byte[] buffer)write(int oneByte)write(byte[] buffer, int offset, int count),通过去阅读 android 源码,可以发现,三者调用关系为 write(byte[] buffer)-> write(byte[] buffer, int offset, int count)-> write(int oneByte),这里直接把源码粘贴过来,方便大家看:

代码语言:javascript复制
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
package java.io;
 
import java.util.Arrays;
 
/**
 * A writable sink for bytes.
 *
 * <p>Most clients will use output streams that write data to the file system
 * ({@link FileOutputStream}), the network ({@link java.net.Socket#getOutputStream()}/{@link
 * java.net.HttpURLConnection#getOutputStream()}), or to an in-memory byte array
 * ({@link ByteArrayOutputStream}).
 *
 * <p>Use {@link OutputStreamWriter} to adapt a byte stream like this one into a
 * character stream.
 *
 * <p>Most clients should wrap their output stream with {@link
 * BufferedOutputStream}. Callers that do only bulk writes may omit buffering.
 *
 * <h3>Subclassing OutputStream</h3>
 * Subclasses that decorate another output stream should consider subclassing
 * {@link FilterOutputStream}, which delegates all calls to the target output
 * stream.
 *
 * <p>All output stream subclasses should override both {@link
 * #write(int)} and {@link #write(byte[],int,int) write(byte[],int,int)}. The
 * three argument overload is necessary for bulk access to the data. This is
 * much more efficient than byte-by-byte access.
 *
 * @see InputStream
 */
public abstract class OutputStream implements Closeable, Flushable {
 
  /**
   * Default constructor.
   */
  public OutputStream() {
  }
 
  /**
   * Closes this stream. Implementations of this method should free any
   * resources used by the stream. This implementation does nothing.
   *
   * @throws IOException
   *       if an error occurs while closing this stream.
   */
  public void close() throws IOException {
    /* empty */
  }
 
  /**
   * Flushes this stream. Implementations of this method should ensure that
   * any buffered data is written out. This implementation does nothing.
   *
   * @throws IOException
   *       if an error occurs while flushing this stream.
   */
  public void flush() throws IOException {
    /* empty */
  }
 
  /**
   * Equivalent to {@code write(buffer, 0, buffer.length)}.
   */
  public void write(byte[] buffer) throws IOException {
    write(buffer, 0, buffer.length);
  }
 
  /**
   * Writes {@code count} bytes from the byte array {@code buffer} starting at
   * position {@code offset} to this stream.
   *
   * @param buffer
   *      the buffer to be written.
   * @param offset
   *      the start position in {@code buffer} from where to get bytes.
   * @param count
   *      the number of bytes from {@code buffer} to write to this
   *      stream.
   * @throws IOException
   *       if an error occurs while writing to this stream.
   * @throws IndexOutOfBoundsException
   *       if {@code offset < 0} or {@code count < 0}, or if
   *       {@code offset   count} is bigger than the length of
   *       {@code buffer}.
   */
  public void write(byte[] buffer, int offset, int count) throws IOException {
    Arrays.checkOffsetAndCount(buffer.length, offset, count);
    for (int i = offset; i < offset   count; i  ) {
      write(buffer[i]);
    }
  }
 
  /**
   * Writes a single byte to this stream. Only the least significant byte of
   * the integer {@code oneByte} is written to the stream.
   *
   * @param oneByte
   *      the byte to be written.
   * @throws IOException
   *       if an error occurs while writing to this stream.
   */
  public abstract void write(int oneByte) throws IOException;
 
  /**
   * Returns true if this writer has encountered and suppressed an error. Used
   * by PrintStreams as an alternative to checked exceptions.
   */
  boolean checkError() {
    return false;
  }
}

所以理论上我们去 hook write(byte[] buffer) 这个方法就可以了,这个 hook 代码代码非常简单,这里就不做展示了,可以看看 hook 结果:

到这里,能够 hook 到,就可以按照我们的需求来修改数据包了,当然,我们也需要找一个 APP 来实战下,在市场上的 APP 是否真的有效。

objection

前一篇文章讲 objection 的使用,这里正好可以用 objection watch 一下 java.io.OutputStream 输出流,发现这里面有 close、flush、write 三个方法

Watch 一下 write 方法,观察 app 在处理业务时,是否有该方法的调用

通过 objection 对 write 方法的跟踪,发现确实 socket 通信调用了 write 方法,而且通过堆栈信息,我们还发现了疑似发送数据包的方法,send、request,这里尝试 hook send 方法,发现果然是有用的,输出信息如下:

综上就是最近遇到的关于 socket 抓包的一点想法和实践,虽然平时测试很少遇到 socket 通信的,但是遇到了,就需要解决不是么?不知道大佬们还有没有更好的思路,如果有,还请告诉我。

0 人点赞