go进阶

Last updated on 5 months ago

go进阶

time包

1
2
3
4
5
6
7
8
time.Now()  //当前时间  2023-08-08 11:15:02.0855663 +0800 CST m=+0.001233401
//打印选定时间,年月日时分秒纳秒时区
t2:=time.Date(2004,1,15,8,26,22,0,time.Local)
fmt.Println(t2)
//2023-08-08 11:17:30.3234989 +0800 CST m=+0.001020901
h, m, s := time.Now().Clock()
fmt.Println(h, m, s)
//11 28 30

file操作

都用os包

1
2
Fileinfo   //返回文件信息和错误值

权限

r w x

读 写 可执行

如果没有的话就是-

-文件 d目录 |连接符

-rwx r-x r-x

-代表文件 rwx代表可读可写可执行 两个r-x分别代表所属组和其它人的权限

八进制表现

r–>004

w–>002

x–>001

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

chomd +x 777 中的777的7就是4+2+1,可读可写可执行

```go
package main

import (
"fmt"
"os"
)

func main() {
err := os.Mkdir("./testss", 0777)
if err != nil {
fmt.Println(err)
}
fmt.Println("创建成功")
file, err := os.Create("123.txt")
if err != nil {
fmt.Println(err)
}
fmt.Println(file) //&{0xc00011c780}
//open是打开文件并建立连接的操作,open打开的文件的权限是只读
file2, err := os.Open("D:/桌面/hack/if_open.txt")
if err != nil {
fmt.Println(err)
}
fmt.Println(file2) //打开文件地址 &{0xc00011ca00}
//关闭文件
err = file2.Close()
if err != nil {
fmt.Println(err)
return
}
fmt.Println("关闭成功")
//删除目录或文件 (不放入回收站,慎用)
err = os.Remove("D:/桌面/hack/if_open.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Println("删除成功")
}

IO操作

input output

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
package main

import (
"fmt"
"io"
"os"
)

func main() {
fn := "D:/桌面/hack/test.txt"
file, err := os.Open(fn)
if err != nil {
fmt.Println(err)
}
bs := make([]byte, 10)
n, err := file.Read(bs)
fmt.Println("这是err", err)
fmt.Println(n)
fmt.Println(bs)
fmt.Println(string(bs))
//读取完整文件
i := -1
for {
i, err = file.Read(bs) //n是实际读取的字节数
if i == 0 || err == io.EOF {
fmt.Println("文件读取结束")
break
}
fmt.Println("i现在是", i, string(bs[:i]))
}

}

ioutil包

现在新版本golang里ioutil包已经被os和io包吞并了

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
package main

import (
"fmt"
"os"
)

func main() {
filename := "D:/桌面/hack/test.txt"
data, err := os.ReadFile(filename)
//完整读取文件内容
fmt.Println(string(data)) //wedfvujabxqwbkdxq64135qpdqwgdq5dqgkuw2541dqw
fmt.Println(err) //nil
fmt.Printf("%T %T", data, err)//[]uint8 <nil>
s := "这是测试还是覆盖"
err = os.WriteFile(filename, []byte(s), os.ModePerm) //这是覆盖函数,不是增加内容
if err != nil {
fmt.Println(err)
return
}
fmt.Println("成功写入")
dirname := "D:/桌面/hack"
dirinfo, err := os.ReadDir(dirname)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("类型是%T", dirinfo)
for i := 0; i < len(dirinfo); i++ {
fmt.Printf("第%d项是%s,是否是目录%t\n", i, dirinfo[i].Name(), dirinfo[i].IsDir())
}
/*
第1项是.user.ini,是否是目录false
第2项是0.jpg,是否是目录false
第3项是1.bat,是否是目录false
第4项是1.jpg,是否是目录false
第5项是1.php,是否是目录false
第6项是ed8a8619567921e6744dc49adc0c4f75.png,是否是目录false
第7项是info.php,是否是目录false
第8项是pass.jpg,是否是目录false
第9项是test.txt,是否是目录false
第10项是whoami.php,是否是目录false
*/
}

并发并行

  • 并发性是多个任务交替执行,共享计算资源,提高吞吐量和响应性。

  • 并行性是多个任务同时在不同的处理器上执行,提高计算速度和性能。

进程线程携程

进程是正在运行的程序

操作系统以进程为单位,进程以线程为单位,一个进程可能包含多个线程

比如打开word文档,word文档是正在运行的一个进程

在word里随便敲一行字,会自动触发开头首字母是否大写和整段话是否有拼写错误的检查,这是两个线程

携程是轻线程

goroutine

携程,封装main函数的是主goroutine

go标识要用的函数,函数不能有返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
)

func main() {
go print_num()
for j := 101; j < 200; j++ {
fmt.Println(j)
}
}

func print_num() {
for i := 0; i < 100; i++ {
fmt.Printf("这是第%d个子线程,i")
}
}
//结果会是交替打印,如果没交替就多打印几次
//可能子线程没打印完,但主线程打印完了,那么子线程不会再继续

net

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

### 扫描器

#### 端口

```go
package main

import (
"fmt"
"net"
)

func main() {
for i := 1; i <= 1024; i++ {
go func(j int) {
addr := fmt.Sprintf("127.0.0.1:%d", j)
con, err := net.Dial("tcp", addr)
if err != nil {
return
}
con.Close()
fmt.Println(j, "端口是打开的")
}(i)
}
}

稍微进阶一点

就是用了goroutine之后,可能有些携程没加载完,但主进程已经加载完了,这个的作用就是每一次循环就记一次数,循环完成之后记的数就是总的携程数,在用wait()函数等待所有携程完成,在结束进程

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
package main

import (
"fmt"
"net"
"sync"
)

func main() {
// 创建一个 sync.WaitGroup 实例,用于等待所有的 goroutines 完成
var ab sync.WaitGroup

// 使用循环,遍历从 0 到 1024 的端口号
for i := 0; i < 1025; i++ {
// 增加等待计数,表示有一个新的 goroutine 需要等待
ab.Add(1)

// 使用 go 关键字开启一个新的 goroutine
go func(j int) {
// 在 goroutine 完成时减少等待计数
defer ab.Done()

// 构造待扫描的 IP 地址和端口号字符串
addr := fmt.Sprintf("127.0.0.1:%d", j)

// 尝试通过 TCP 协议连接到指定地址
con, err := net.Dial("tcp", addr)
if err != nil {
// 如果连接失败,表示端口是关闭的,函数直接返回
return
}
// 连接成功,关闭连接
con.Close()

// 输出端口号是开放的信息
fmt.Printf("%d is open\n", j)
}(i) // 传递端口号 i 给匿名函数的参数 j
}

// 等待所有的 goroutines 完成
ab.Wait()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"net"
"sync"
)
func worker(ports chan int,wg *sync.WaitGroup){
for p:=range ports{
fmt.Println(p)
wg.Done()
}
}
func main() {
ports:=make(chan int,100)
var wg sync.WaitGroup
for i:=0;i<cap(ports);i++{
wg.Add(1)
ports<-i
}
wg.Wait()
close(ports)
}


等待组初步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"sync"
"time"
)

func pro(i int, wg *sync.WaitGroup) {
fmt.Printf("进程%d开始\n", i)
time.Sleep(2 * time.Second)
fmt.Printf("进程%d结束\n", i)
wg.Done()
}
func main() {
cou := 3
var wg sync.WaitGroup
for i := 0; i < cou; i++ {
wg.Add(1)
go pro(i, &wg)
}
wg.Wait()
fmt.Println("main stop")
}

工人池

一个理论上能用的扫描器

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
package main

import (
"fmt"
"net"
"sort"
)

func main() {
ports := make(chan int, 100)
results := make(chan int)
var openports []int
for i := 0; i < cap(ports); i++ {
go worker(ports, results)
}
go func() {
for i := 1; i <= 1024; i++ {
ports <- i
}
}()
for i := 0; i < 1024; i++ {
port := <-results
if port != 0 {
openports = append(openports, port)
}
}
close(ports)
close(results)
sort.Ints(openports)
for _, port := range openports {
fmt.Printf("%d is open\n", port)
}
}
func worker(ports, results chan int) {
for p := range ports {
addr := fmt.Sprintf("127.0.0.1:%d", p)
//返回值是一个结构体+一个错误,后面的conn.Close()是结构体.方法的类型
conn, err := net.Dial("tcp", addr)
if err != nil {
results <- 0
continue
}
//这里的close是关闭与端口的连接,而不是关闭一个通道
conn.Close()
results <- p
}
}

年轻人的第一款自制扫描器

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
package main

import (
"fmt"
"net"
"sort"
"sync"
)

func main() {
fmt.Print("请输入要扫描的IP地址: ")
var ip string
fmt.Scanln(&ip)
fmt.Println("给你一点加载时间,你才知道是真的扫描而不是面向结果编程")
var wg sync.WaitGroup
ops := []int{}
ports := make(chan int, 5000)
for i := 1; i <= 5000; i++ {
wg.Add(1)
go func(j int) {
defer wg.Done() //这句一定要放前面,确保被程序看到但是推迟了,如果放最后,return之后会直接看不见done
addr := fmt.Sprintf("%s:%d", ip, j)
conn, err := net.Dial("tcp", addr)
if err != nil {
return
}
ports <- j
conn.Close()

}(i)
}
fmt.Println("正在检测")
wg.Wait()
close(ports)
for v := range ports {
ops = append(ops, v)
}
sort.Ints(ops)
for _, v := range ops {
fmt.Printf("%d port is open\n", v)
}
fmt.Println("按下回车键以退出程序...")
fmt.Scanln()
}
worker pool的实现
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
package main

import (
"fmt"
"net"
"sort"
)

func worker(ports_chan, results_chan chan int) {
for port := range ports_chan {
//这里必须加一个addr来表示端口,下面的net.Dial()需要tcp和ip两个变量,直接 con, err := net.Dial("tcp",":%d",port)会报错
addr := fmt.Sprintf(":%d", port)
con, err := net.Dial("tcp", addr)
if err != nil {
results_chan <- 0
continue
}
results_chan <- port
con.Close()
}
}

func main() {
ports_chan := make(chan int, 5000)
results_chan := make(chan int)
open_ports := make([]int, 0, 30)
for i := 1; i <= cap(ports_chan); i++ {
go worker(ports_chan, results_chan)
}
go func() {
for i := 1; i <= 5000; i++ {
ports_chan <- i
}
}()

/*
for port := range results_chan {
if port != 0 {
open_ports = append(open_ports, port)
}
}
*/

for i := 1; i <= 5000; i++ {
port := <-results_chan
if port != 0 {
open_ports = append(open_ports, port)
}
}
close(ports_chan)
close(results_chan)
sort.Ints(open_ports)
for _, p := range open_ports {
fmt.Printf("%d port is open\n", p)
}
}

tcp代理

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
package main

import (
"io"
"log"
"net"
)

func main() {
listener, err := net.Listen("tcp", ":20080")
if err != nil {
log.Fatalln("绑不了端口")
}
log.Println("监听端口20080")
for {
c, err := listener.Accept()
log.Println("接收连接")
if err != nil {
log.Fatalln("连不上")
}
go echo(c)
}
}
func echo(c net.Conn) {
defer c.Close()
b := make([]byte, 512)
for {
size, err := c.Read(b[0:])
if err == io.EOF {
log.Println("未知错误")
break
}
log.Printf("接收到了%d byte的数据,数据是:%s\n", size, string(b))
log.Println("写数据")
if _, err := c.Write(b[0:size]); err != nil {
log.Fatalln("写不了")
}
}
}

缓冲

端口转发器

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
package main

import (
"io"
"log"
"net"
)

func main() {
//本地端口80监听
listener, err := net.Listen("tcp", ":80")
if err != nil {
log.Fatalln("unable to bind port")
}
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal("unable to accept connection")
}
go handle(conn)
}
}
func handle(src net.Conn) {
dst, err := net.Dial("tcp", "joescatcam.website:80")
if err != nil {
log.Fatalln("unable to connect our unreachable host")
}
defer dst.Close()
go func() {
//将源的输出复制到目标
if _, err := io.Copy(src, dst); err != nil {
log.Fatalln(err)
}
}()
//将输出的目标复制回源
if _, err := io.Copy(src, dst); err != nil {
log.Fatalln(err)
}
}

反向shell(不是反弹shell)

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
package main

import (
"io"
"log"
"net"
"os/exec"
)

func handle(c net.Conn) {
//exec.Command()有两个参数和一个返回值,返回值是cmd结构体指针,第一个参数是要打开的终端的路径,这里是本机开放端口允许外部连接,所以是cmd.exe,
//如果本机是linux,那就是"/bin/sh","-i",-i是以交互模式运行终端,所以第二个参数就是可执行的参数,比如-h,--version
cmd := exec.Command("cmd.exe")
//func Pipe() (*PipeReader, *PipeWriter)
rp, wp := io.Pipe()
//以下两个cmd操作不是在赋值,而是进行一个重定向操作
cmd.Stdin = c
cmd.Stdout = wp
go io.Copy(c, rp)
cmd.Run()
c.Close()
}
func main() {
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatalln(err)
}
conn, err := listener.Accept()
if err != nil {
log.Fatalln(err)
}
go handle(conn)
}

http请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main
import(
"fmt"
"io" //读取操作的包
"net/http"
"log" //如有err直接退出
)
func main(){
resp,err:=http.Get("https://blog.yblue.top") //http.Get("")中存入url,返回值是*http.Response的结构体指针,resp下有很多种属性,比如状态(Status),会返回状态码和状态(200 OK),StatusCode会只返回状态码,Body是http报文中的响应主体
//这里也可以直接进行get请求,就在url里ga
if err!=nil{
log.Fatalln(err)
}
defer resp.Body.Close()
body,err:=io.ReadAll(resp.Body) //这里反回的body是[]byte,是一个字节类型的切片
if err!=nil{
log.Fatalln(err)
}
fmt.Println(string(body))
fmt.Println(resp.Status)
if resp.StatusCode ==200{
fmt.Println("OK")
}
}

如果将请求放到内容里而不是直接在url路径里

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
package main
import(
"fmt"
"log"
"net/http"
"io"
"net/url"
)
func main(){
url_object:=url.Value{} //创建了一个空的 url.Values 对象,可以用于存储 URL 的查询参数,这里是引用,url.Values{}是个指针
Url,err:=url.Parse("https://baidu.com")
//func url.Parse(rawURL string) (*url.URL, error) 将baidu.com转化成一个url.URL类型的指针并返回,对这个对象可以直接进行获取信息,此时url_object对应的就是https://baidu.com
if err!=nil{
log.Fatalln(err)
}
url_object.Set("name","xiaoming") //set是覆盖,add是添加
url_object.Set("age","12")
url_object.Set("name","taosu")
url_object.Add("age","13")
Url.RawQuery=url_object.Encode() //这个函数是一个类似整合的函数,前面set或者add的键值对,在使用过这个函数后,会被编码进url中并用&连接起来,形成查询参数字符串
urlPath:=Url.String() //Url现在是指针, .String()是将url以字符串的形式输出出来
fmt.Println("请求的路径是:",urlPath)
resp,err:=http.Get(urlPath) //对url发起请求
defer resp.Body.Close()
if err!=nil{
log.Fatalln(err)
}
body,err:=io.ReadAll(resp.Body)
if err!=nil{
log.Fatalln(err)
}
fmt.Println(string(body)) //这里必须用string()函数,否则会直接打印ascii码而不是字符串
}
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
url.Values{} 是 Go 语言中 net/url 包提供的一个类型,用于表示 URL 查询参数的集合。

url.Values 是一个类似于字典的结构,它可以存储一系列的键值对,其中键和值都是字符串类型。它提供了一组方法来设置、获取和操作查询参数。

以下是一些常用的 url.Values 方法:

Set(key, value):设置指定键的查询参数值。如果该键已存在,则会用新的值覆盖旧的值。如果该键不存在,则会将键值对添加到查询参数中。
Add(key, value):向查询参数中添加一个新的键值对,不管该键是否已存在。如果该键已存在,则会将新的值追加到已有值的末尾,用 & 符号分隔。
Get(key):获取指定键的查询参数值。如果该键不存在,则返回空字符串。
Del(key):删除指定键的查询参数。
Encode():将 url.Values 对象编码为查询参数字符串。

//实例
package main

import (
"fmt"
"io"
"log"
"net/http"
"net/url"
)

func main() {
url_object := url.Values{}
Url, err := url.Parse("https://blog.yblue.top")
if err != nil {
log.Fatalln(err)
}
url_object.Set("username", "admin")
Url.RawQuery = url_object.Encode()
UrlPath := Url.String()
resp, err := http.Get(UrlPath)
if err != nil {
log.Fatalln(err)
}
defer resp.Body.Close()
Body, _ := io.ReadAll(resp.Body)
fmt.Println(Body)
fmt.Println("utl的路径是:", UrlPath, "\n", "查询语句是:", Url.RawQuery)
}

http请求且写入请求头内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"io"
"net/http"
)

func main() {
client := http.Client{} //Client是http包中定义的一个结构体,结构体可以通过结构体名字{}来创建一个对象
req, _ := http.NewRequest("GET", "http://blog.yblue.top", nil) //http.Get是创造请求并发送,而这里是先创建请求
req.Header.Add("name", "Paul_Chan")
req.Header.Add("age", "26")
resp, _ := client.Do(req) //这一步是发送请求
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}

打印http请求头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"net/http"
"net/http/httputil"
)

func main() {
req, _ := http.NewRequest("GET", "http://blog.yblue.top", nil)
req.Header.Add("name", "Paul_Chan")
req.Header.Add("age", "26")
req.Header.Add("x-forwarded-for", "127.0.0.1")
// 打印所有的请求头
requestDump, _ := httputil.DumpRequestOut(req, true)
fmt.Println(string(requestDump))

// 发起实际的请求
client := http.DefaultClient
_,_ = client.Do(req)
}

go进阶
https://blog.yblue.top/2023/08/20/go进阶/
Posted on
August 20, 2023
Licensed under