使用Android Studio编写一个网络抓包小工具


我们在开发自己的Android项目的时候会经常做网络调试,我们可以自己制作一个抓包小工具来帮助我们测试网络数据,更能加深对网络数据传输过程的理解,以下是使用Android Studio编写的一个简单的Android网络抓包工具的实现过程。

首先,在Android Studio中创建一个新的Android项目。您可以按照常规的步骤进行创建,并在创建项目时选择适当的项目名称、包名称和应用程序的其他细节。确保您选择最新的Android SDK版本,并选择空Activity模板。

步骤1:添加依赖项

在项目的build.gradle文件中,我们需要添加以下依赖项:
dependencies {
    implementation 'org.pcap4j:pcap4j-core:1.9.1'
    implementation 'org.pcap4j:pcap4j-packetfactory-static:1.9.1'
}

这些依赖项将我们需要使用的Pcap4J库添加到我们的项目中。

步骤2:创建UI

为您的应用程序创建一个简单的布局文件,以显示捕获的网络数据包。您可以在res/layout/目录下创建一个新的布局文件。
在MainActivity中,我们将创建一个RecyclerView来显示捕获到的数据包。我们还将添加一个Clear按钮,用于清除数据包列表。以下是我们activity_main.xml示例布局文件的代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:scrollbars="vertical" />
    <Button
        android:id="@+id/btnClear"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Clear" />
</LinearLayout>

步骤3:创建数据包模型

为了在RecyclerView中显示数据包,我们需要创建一个数据包模型。我们将创建一个PacketModel类,其中包含数据包的各个属性,例如时间戳、源IP地址、目标IP地址、源端口、目标端口和数据大小。
public class PacketModel {
    private String timestamp;
    private String srcIp;
    private String dstIp;
    private int srcPort;
    private int dstPort;
    private int length;
    public PacketModel(String timestamp, String srcIp, String dstIp, int srcPort, int dstPort, int length) {
        this.timestamp = timestamp;
        this.srcIp = srcIp;
        this.dstIp = dstIp;
        this.srcPort = srcPort;
        this.dstPort = dstPort;
        this.length = length;
    }
    public String getTimestamp() {
        return timestamp;
    }
    public String getSrcIp() {
        return srcIp;
    }
    public String getDstIp() {
        return dstIp;
    }
    public int getSrcPort() {
        return srcPort;
    }
    public int getDstPort() {
        return dstPort;
    }
    public int getLength() {
        return length;
    }
}

步骤4:创建RecyclerView适配器

接下来,我们需要创建一个RecyclerView适配器来管理数据包列表。我们将创建一个PacketAdapter类,该类扩展RecyclerView.Adapter类,并重写必要的方法。
public class PacketAdapter extends RecyclerView.Adapter {
    private List mPackets;
    public PacketAdapter(List packets) {
        mPackets = packets;
    }
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.packet_item, parent, false);
        return new ViewHolder(view);
    }
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        PacketModel packet = mPackets.get(position);
        holder.timestampTextView.setText(packet.getTimestamp());
        holder.srcIpTextView.setText(packet.getSrcIp());
        holder.dstIpTextView.setText(packet.getDstIp());
        holder.srcPortTextView.setText(String.valueOf(packet.getSrcPort()));
        holder.dstPortTextView.setText(String.valueOf(packet.getDstPort()));
        holder.lengthTextView.setText(String.valueOf(packet.getLength()));
    }
    @Override
    public int getItemCount() {
        return mPackets.size();
    }
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView timestampTextView;
        public TextView srcIpTextView;
        public TextView dstIpTextView;
        public TextView srcPortTextView;
        public TextView dstPortTextView;
        public TextView lengthTextView;
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            timestampTextView = itemView.findViewById(R.id.timestampTextView);
            srcIpTextView = itemView.findViewById(R.id.srcIpTextView);
            dstIpTextView = itemView.findViewById(R.id.dstIpTextView);
            srcPortTextView = itemView.findViewById(R.id.srcPortTextView);
            dstPortTextView = itemView.findViewById(R.id.dstPortTextView);
            lengthTextView = itemView.findViewById(R.id.lengthTextView);
        }
    }
}

步骤5:创建抓包方法

现在,我们需要编写代码来捕获网络数据包。我们将在MainActivity中创建一个方法来执行此操作。以下是我们的MainActivity.java文件的代码:
public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();
    private static final int SNAPLEN = 65536;
    private static final int READ_TIMEOUT = 10;
    private static final int BUFFER_SIZE = 1024 * 1024;
    private RecyclerView mRecyclerView;
    private Button mBtnClear;
    private List mPackets = new ArrayList<>();
    private PacketAdapter mAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRecyclerView = findViewById(R.id.recyclerView);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mAdapter = new PacketAdapter(mPackets);
        mRecyclerView.setAdapter(mAdapter);
        mBtnClear = findViewById(R.id.btnClear);
        mBtnClear.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPackets.clear();
                mAdapter.notifyDataSetChanged();
            }
        });
        startCapture();
    }
    private void startCapture() {
        try {
            // 获取网络接口
            List allDevs = Pcaps.findAllDevs();
            if (allDevs.isEmpty()) {
                Log.e(TAG, "No network interface found.");
                return;
            }
            // 选择网络接口
            PcapNetworkInterface nif = allDevs.get(0);
            // 打开网络接口
            final PacketCapture packetCapture = nif.openLive(SNAPLEN, PromiscuousMode.PROMISCUOUS, READ_TIMEOUT);
            // 开始捕获数据包
            packetCapture.loop(-1, new PacketListener() {
                @Override
                public void gotPacket(Packet packet) {
                    // 解析数据包
                    byte[] data = packet.getPayload().getRawData();
                    if (data != null && data.length > 0) {
                        String timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()).format(new Date(packet.getCaptureHeader().timestampInMillis()));
                        String srcIp = packet.getHeader(IpV4Packet.class).getHeader().getSrcAddr().getHostAddress();
                        String dstIp = packet.getHeader(IpV4Packet.class).getHeader().getDstAddr().getHostAddress();
                        int srcPort = packet.getHeader(TcpPacket.class).getHeader().getSrcPort().valueAsInt();
                        int dstPort = packet.getHeader(TcpPacket.class).getHeader().getDstPort().valueAsInt();
                        int length = data.length;
                        PacketModel packetModel = new PacketModel(timestamp, srcIp, dstIp, srcPort, dstPort, length);
                        mPackets.add(packetModel);
                        // 更新UI
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                mAdapter.notifyItemInserted(mPackets.size() - 1);
                                mRecyclerView.smoothScrollToPosition(mPackets.size() - 1);
                            }
                        });
                    }
                }
            });
        } catch (PcapNativeException e) {
            Log.e(TAG, "Error while opening device: " + e.getMessage());
        } catch (InterruptedException e) {
            Log.e(TAG, "Error while capturing packets: " + e.getMessage());
        }
    }
}

我们首先获取网络接口,然后选择第一个接口。接着,我们打开接口并使用PacketListener捕获数据包。对于每个捕获的数据包,我们解析它并将其添加到mPackets列表中。最后,我们在UI线程中通知适配器,以便更新UI列表并滚动到最后一个条目。

在onCreate()方法中,我们设置RecyclerView和适配器,并为清除按钮添加一个点击监听器。然后,我们调用startCapture()方法开始捕获数据包。

最后,我们需要在清单文件中声明Internet权限,以便我们可以访问网络:

<uses-permission android:name="android.permission.INTERNET" />

现在,我们可以编译并运行应用程序了。在模拟器或实际设备上运行应用程序后,您应该可以看到列表开始填充数据包。如果您打开设备的浏览器并浏览一些网页,您应该能够看到抓取的HTTP请求和响应。

这是一个简单的抓包应用程序示例,您可以在此基础上添加更多功能和改进,例如过滤器,更高级的解析,导出功能等等。


完整代码如下所示:

public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private PacketAdapter adapter;
    private List packetList = new ArrayList<>();
    private Button clearButton;
    private PacketCapture packetCapture;
    private static final int SNAP_LEN = 65536;
    private static final int READ_TIMEOUT = 1000;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recyclerView = findViewById(R.id.recyclerView);
        adapter = new PacketAdapter(packetList);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(adapter);
        clearButton = findViewById(R.id.clearButton);
        clearButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                packetList.clear();
                adapter.notifyDataSetChanged();
            }
        });
        startCapture();
    }
    private void startCapture() {
        try {
            NetworkInterface nif = PcapNetworkInterface.getSelectedInterface();
            packetCapture = nif.openLive(SNAP_LEN, PromiscuousMode.PROMISCUOUS, READ_TIMEOUT);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!Thread.currentThread().isInterrupted()) {
                        Packet packet = packetCapture.getNextPacket();
                        if (packet != null) {
                            packetList.add(packet);
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    adapter.notifyItemInserted(packetList.size() - 1);
                                    recyclerView.scrollToPosition(packetList.size() - 1);
                                }
                            });
                        }
                    }
                }
            }).start();
        } catch (PcapNativeException e) {
            e.printStackTrace();
        } catch (NotOpenException e) {
            e.printStackTrace();
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (packetCapture != null) {
            packetCapture.breakLoop();
            packetCapture.close();
        }
    }
}

这是Packet类:
public class Packet {
    private String time;
    private String sourceIp;
    private String destinationIp;
    private String protocol;
    private String length;
    public Packet(String time, String sourceIp, String destinationIp, String protocol, String length) {
        this.time = time;
        this.sourceIp = sourceIp;
        this.destinationIp = destinationIp;
        this.protocol = protocol;
        this.length = length;
    }
    public String getTime() {
        return time;
    }
    public String getSourceIp() {
        return sourceIp;
    }
    public String getDestinationIp() {
        return destinationIp;
    }
    public String getProtocol() {
        return protocol;
    }
    public String getLength() {
        return length;
    }
}

这是PacketAdapter类:
public class PacketAdapter extends RecyclerView.Adapter {
    private List packetList;
    public PacketAdapter(List packetList) {
        this.packetList = packetList;
    }
    @NonNull
    @Override
    public PacketViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.packet_item, parent, false);
        return new PacketViewHolder(view);
    }
    @Override
    public void onBindViewHolder(@NonNull PacketViewHolder holder, int position) {
        Packet packet = packetList.get(position);
        holder.timeTextView.setText(packet.getTime());
        holder.sourceIpTextView.setText(packet.getSourceIp());
        holder.destinationIpTextView.setText(packet.getDestinationIp());
        holder.protocolTextView.setText(packet.getProtocol());
        holder.lengthTextView.setText(packet.getLength());
    }
    @Override
    public int getItemCount() {
        return packetList.size();
    }

 static class PacketViewHolder extends RecyclerView.ViewHolder {
        private TextView timeTextView;
        private TextView sourceIpTextView;
        private TextView destinationIpTextView;
        private TextView protocolTextView;
        private TextView lengthTextView;
        public PacketViewHolder(@NonNull View itemView) {
            super(itemView);
            timeTextView = itemView.findViewById(R.id.timeTextView);
            sourceIpTextView = itemView.findViewById(R.id.sourceIpTextView);
            destinationIpTextView = itemView.findViewById(R.id.destinationIpTextView);
            protocolTextView = itemView.findViewById(R.id.protocolTextView);
            lengthTextView = itemView.findViewById(R.id.lengthTextView);
        }
    }
}
最后,还需要在AndroidManifest.xml文件中添加网络权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

这样,我们就完成了一个简单的网络抓包应用程序。当我们运行程序时,它将开始捕获在手机网络接口上传输的所有数据包,并将它们显示在一个RecyclerView列表中。我们还添加了一个清除按钮,以便在需要时清空列表。

请注意,这只是一个简单的示例应用程序,仅用于教学目的。实际上,您可能需要对网络数据包进行更详细的分析,并根据您的具体需求进行定制。此外,在使用网络抓包应用程序时,请务必遵守所有相关法律和规定,以免触犯隐私权和网络安全等问题。

希望这篇文章对您有所帮助!