概述

在上一篇《以美国和韩国为目标的Kimsuky最新钓鱼活动攻击组件及木马攻击链剖析》文章中,笔者挖掘发现了Kimsuky组织使用Powershell加载加密Xeno-RAT远控程序的最新攻击链流程,同时,在报告中笔者对Xeno-RAT远控程序做了简单的功能分析。

为了能够进一步的对Xeno-RAT远控程序进行剖析,笔者基于网络调研发现Xeno-RAT远控程序其实是一款开源远控。因此,为了能够实现对Xeno-RAT远控程序的进一步研究,笔者准备从如下几个角度复现Xeno-RAT远控程序的攻击场景及攻击利用过程中的Xeno-RAT远控程序的通信模型:

  • 开源Xeno-RAT利用分析:对Xeno-RAT开源项目进行简单分析,模拟利用Xeno-RAT开源远控程序;
  • Xeno-RAT配置信息提取:对Xeno-RAT远控程序的配置信息进行提取;
  • Xeno-RAT功能分析:对Xeno-RAT远控程序的功能原理进行剖析;
  • Xeno-RAT通信模型分析:基于模拟构建的通信解密程序的解密结果对Xeno-RAT远控程序的通信模型进行剖析;
  • 模拟构建通信解密程序:模拟构建通信解密程序,实现对Xeno-RAT木马通信数据的多个会话进行批量解密;

开源Xeno-RAT利用分析

由于Xeno-RAT是开源远控工具,因此直接访问github(https://github.com/moom825/xeno-rat)即可对其工具源码、release版本程序进行下载研究。

通过对开源项目进行简单分析,发现:

  • Xeno-RAT远控工具于2023年10月17日首次发布,目前共推出10次迭代,最新版本为2024年2月15日发布的Xeno-RAT v1.8.7版本;
  • Xeno-RAT开源项目的维护人员较少,目前只有1人维护,694余个标星,220个账号forks拷贝项目;
  • Xeno-RAT远控工具的所有模块均为C#代码编写,比较核心的模块为Client端、Server端、Plugin插件模块;
  • Xeno-RAT远控工具的远控功能较全面,常见的远控功能均支持;

相关截图如下:

生成Client端木马

下载release版本程序并直接运行即可打开Xeno-RAT控制端的GUI界面。

在GUI界面中选择【Builder】菜单即可对Xeno-RAT Client端木马进行自定义配置,相关截图如下:

木马上线

在GUI界面中选择【Listener】菜单即可自定义配置Xeno-RAT Server端监听端口及通信加密密码,相关截图如下:

在受控主机中运行Xeno-RAT Client端木马程序,即可成功实现木马上线,上线后即可实现对受控主机的远控管理,相关截图如下:

Xeno-RAT配置信息提取

由于远控木马程序可根据攻击者意图进行自定义配置,因此,直接提取远控木马程序的配置信息即可快速梳理远控木马程序的整体运行逻辑。

通过分析,笔者发现,在xeno_rat_client命令空间的Program类中,存在大量的配置信息数据,经过分析,发现此数据即为生成Xeno-RAT端木马环节中的自定义数据。进一步分析,发现除通信加密密码配置信息并非明文数据外,其余配置信息均为明文数据。相关截图如下:

尝试对通信加密密码配置信息进行分析,发现不同通信加密密码配置信息对应的EncryptionKey数据均不同,因此,笔者推测EncryptionKey数据即为通信加密密码配置信息经过一系列运算得到的数据。进一步分析,笔者发现EncryptionKey数据是由通信加密密码配置信息SHA256运算所得数据,相关截图如下:

Xeno-RAT功能分析

配置信息

通过分析,发现xeno rat server.exe控制端程序的当前目录中,存在一个Config.json文件,此文件的内容为配置信息内容,每次使用控制端程序修改自定义配置时,此文件内容也会跟随修改,相关截图如下:

生成Client木马的原理

通过分析,发现xeno rat server.exe控制端程序当前路径的stub目录下,存在一个xeno rat client.exe程序,相关截图如下:

进一步分析,发现在控制端GUI界面中的【Builder】菜单中点击【Build】按钮时,控制端将根据自定义配置修改并另存stub目录下的xeno rat client.exe程序内容,相关代码截图如下:

加密通信

通过分析,梳理Xeno-RAT木马的通信加解密逻辑如下:

  • 通信加密
    • 调用AES算法对数据进行加密
    • 调用ntdll.dll的RtlCompressBuffer函数对数据进行压缩
  • 通信解密
    • 调用ntdll.dll的RtlDecompressBuffer函数对数据进行解压缩
    • 调用AES算法对数据进行解密

通信加解密整体框架代码截图如下:

AES加解密代码截图如下:

导入ntdll.dll文件的RtlCompressBuffer、RtlDecompressBuffer函数代码截图如下:

远控功能

通过分析,发现Xeno-RAT的远控功能主要是通过动态加载插件DLL程序实现的,相关代码截图如下:

相关插件文件截图如下:

相关功能插件对应菜单功能梳理如下:

功能插件DLL文件名 对应菜单功能
Chat.dll Fun->Chat
File manager.dll System->File Manager
Fun.dll Fun
Hvnc.dll Surveillance->Hvnc
InfoGrab.dll System->InfoGrab
KeyLogger.dll Surveillance->Key Logger
KeyLoggerOffline.dll Surveillance->Offline Key Logger
LiveMicrophone.dll Surveillance->Live Microphone
ProcessManager.dll System->Process Manager
Registry Manager.dll System->Registry Manager
ReverseProxy.dll System->Reverse proxy
ScreenControl.dll Surveillance->Screen Control
Shell.dll System->Shell
Startup.dll System->Startup
SystemPower.dll Power
Uacbypass.dll Uac Bypass
WebCam.dll Surveillance->WebCam

Xeno-RAT通信模型分析

通信模型剖析

通过分析,梳理Xeno-RAT木马的通信模型,发现通信模型可分为三类:

  • 第一类载荷:
    • 原始数据:4字节后续载荷长度+1字节标志数据+AES加密数据
  • 第二类数据:
    • 原始数据:4字节后续载荷长度+1字节标志数据+AES加密数据
    • AES加密数据:1字节标志数据+载荷数据
  • 第三类数据:
    • 原始数据:4字节后续载荷长度+1字节标志数据+AES加密数据
    • AES加密数据:1字节标志数据+4字节后续载荷长度 +载荷数据

实际通信数据包案例剖析:

#********************第一类载荷********************
#通信载荷
71000000002bb7e12d9a587b922b3950cc8eba3f0c8191751387daa29b48cd81eaea31ee59b095e128eeef27b28528c31e5f1843221a9b48144273014dccac0e67e6839a86e95ff185e2721b9bd628d263f762fb781243b0ba48683378288fbadc04f919ad9440ece7b3542ffdcd720e0269eda541
#数据结构解析
71000000    #后续载荷长度
00  #标志数据00,直接AES解密
    #AES加密数据
2bb7e12d9a587b922b3950cc8eba3f0c8191751387daa29b48cd81eaea31ee59b095e128eeef27b28528c31e5f1843221a9b48144273014dccac0e67e6839a86e95ff185e2721b9bd628d263f762fb781243b0ba48683378288fbadc04f919ad9440ece7b3542ffdcd720e0269eda541
    #AES解密数据
8c32e9fbd0ffa3630c8931a538dbfceee0543cc6e0d9598322a3e0c372e9b3f42e2591c23177a3edf6fd8f2979694ca51abb3411f647e5b2d966d92c1edff023d63d77124939bd69b14fd0e3492f09b8573ba9b6b5a1fee73fdee9ca9d76ce160c6c533c

#********************第二类载荷********************
#通信载荷
91000000034733d8cb82a565b5ba859e70fd731f8a427798047feeb77cccf56296ad7ca6605add3348706fe5dcdff433711eda0a9f017608a8a57c376e5678332d418e81446767d2f9f25dfee27c8dacb63a3a6ffcfb64bfe5784629b48d3a51a96e77945c5010009771867914f19798b597127034781da3cdc21f8b603094520b43247122776c38db81c5dab1824bb4335f801570
#数据结构解析
91000000    #后续载荷长度
03  #标志数据03,先AES解密,然后根据解密后的数据标志,选择是否RtlCompressBuffer解压缩
    #AES加密数据
4733d8cb82a565b5ba859e70fd731f8a427798047feeb77cccf56296ad7ca6605add3348706fe5dcdff433711eda0a9f017608a8a57c376e5678332d418e81446767d2f9f25dfee27c8dacb63a3a6ffcfb64bfe5784629b48d3a51a96e77945c5010009771867914f19798b597127034781da3cdc21f8b603094520b43247122776c38db81c5dab1824bb4335f801570
    #AES解密数据
0038343746374438303233323042423437303442380061646d696e004445534b544f502d41313152424c385c61646d696e00312e382e37004d6963726f736f66742057696e646f777320313020e4b893e4b89ae78988202d20363420e4bd8d0057696e646f777320446566656e6465722c20e781abe7bb92e5ae89e585a8e8bdafe4bbb60046616c7365

#数据结构解析
00  #标志数据,00:不使用RtlCompressBuffer解压缩;01:使用RtlCompressBuffer解压缩
38343746374438303233323042423437303442380061646d696e004445534b544f502d41313152424c385c61646d696e00312e382e37004d6963726f736f66742057696e646f777320313020e4b893e4b89ae78988202d20363420e4bd8d0057696e646f777320446566656e6465722c20e781abe7bb92e5ae89e585a8e8bdafe4bbb60046616c7365

#********************第三类载荷********************
#通信载荷
a123000003766ed02ee3d62c3d40bb97a3e99b2bdc5e35c3d646aa6279d22a37ab0ce87ebc790839fa0e0484538c10a5702ad181c0f2be92fb50586b6f7b008a1669cae...省略117760字节...d2770b8a52c5542998ad1e2c3c8b800c9ae75dfe8049ba8fbe737c9c41e800afb2e2ed1c9d5348558675f76e7245aeff284a5b6353384c0260033bdb228cb0c25df8308ada38048ffbd91dbedfd23f8b238f0c5694c853dec54198e41f14878765a18c2c18e0985c9c59fe294f31c22fb0655f82411f1e6b4748e8a65e8ab23ccc8bf6218fc0f0da06ca49a0e73d0bd3d37ff2c9baf6437d5f6ff9d2a1e0a26911455c20ad3f34b1ffa6ea2e1755364c92b
#数据结构解析
a1230000    #后续载荷长度
03  #标志数据03,先AES解密,然后根据解密后的数据标志,选择是否RtlCompressBuffer解压缩
    #AES加密数据
766ed02ee3d62c3d40bb97a3e99b2bdc5e35c3d646aa6279d22a37ab0ce87ebc790839fa0e0484538c10a5702ad181c0f2be92fb50586b6f7b008a1669cae...省略117760字节...d2770b8a52c5542998ad1e2c3c8b800c9ae75dfe8049ba8fbe737c9c41e800afb2e2ed1c9d5348558675f76e7245aeff284a5b6353384c0260033bdb228cb0c25df8308ada38048ffbd91dbedfd23f8b238f0c5694c853dec54198e41f14878765a18c2c18e0985c9c59fe294f31c22fb0655f82411f1e6b4748e8a65e8ab23ccc8bf6218fc0f0da06ca49a0e73d0bd3d37ff2c9baf6437d5f6ff9d2a1e0a26911455c20ad3f34b1ffa6ea2e1755364c92b
    #AES解密数据
01004200006fb8004d5a90000300000082040030ffff0000b800382d0100400438190080000c0e1f00ba0e00b409...省略17880字节...4c7001671bd203510e70f00d910b670068479005703a3e012000a930002024003270083200a04a002a3732127704931a64f000b1077200a26bd61400004a720b4ff4047f911a1103d50ad100ff0bff0bf30b3a5572175050016f500175301774f7ba193f04f71634f216bb035d173d17aa38320341100273b20d62d005fe79d0057f037b030f000f000f000f00030f000f000fb000005000000c0000000c5c390040f001

#数据结构解析
01  #标志数据,00:不使用RtlCompressBuffer解压缩;01:使用RtlCompressBuffer解压缩
00420000    #后续载荷长度,0x4200
6fb8004d5a90000300000082040030ffff0000b800382d0100400438190080000c0e1f00ba0e00b409...省略17880字节...4c7001671bd203510e70f00d910b670068479005703a3e012000a930002024003270083200a04a002a3732127704931a64f000b1077200a26bd61400004a720b4ff4047f911a1103d50ad100ff0bff0bf30b3a5572175050016f500175301774f7ba193f04f71634f216bb035d173d17aa38320341100273b20d62d005fe79d0057f037b030f000f000f000f00030f000f000fb000005000000c0000000c5c390040f001

#RtlCompressBuffer解压缩
4D5A90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000800000000E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F742062652072756E20696E20444F53206D6F64652E0D0D0A24000000...省略33544字节...

会话通信流程梳理

对Xeno-RAT木马的通信数据进行剖析,梳理Xeno-RAT木马的通信模型,发现其通信流程如下:

  • Xeno-RAT运行后,将开启第一个会话通信,此会话通信主要用于获取系统基本信息,即控制端上显示的被控端基本信息;
  • Xeno-RAT获取基本信息后,将开启第二个会话通信,此会话通信主要用于心跳连接及开启第三个主会话通信;
  • Xeno-RAT第三个会话通信即为主会话通信,此会话通信主要用于传输执行远控指令等;
  • 当攻击者在控制端中点击运行特定菜单功能时,Xeno-RAT将根据不同功能传输并运行Plugins目录中的对应dll文件;
    • 不同功能插件开启的子会话数量不同
    • FileManager功能插件:将开启两个会话,第一个会话通信主要用于传输功能插件的载荷内容,第二个会话通信主要用于此功能插件的通信功能;
    • ProcessManager功能插件:将开启一个会话,主要用于传输功能插件的载荷内容及功能插件的通信数据;

第一个会话的会话通信内容截图如下:

第二个会话的会话通信内容截图如下:

第三个会话的会话通信内容截图如下:

FileManager功能插件的会话通信内容截图如下:

ProcessManager功能插件的会话通信内容截图如下:

模拟构建通信解密程序

为了更便利的对Xeno-RAT远控工具的通信数据进行解密,笔者尝试编写了一个解密程序,可对Xeno-RAT木马通信数据的多个会话进行批量解密,解密效果如下:

自动化脚本输入文件格式如下:(备注:直接从wireshark导出”C Arrays“数据即可)

将多个会话的多个会话载荷文件保存至同一目录,即可实现对目录下的所有会话进行批量解密的效果,会话载荷目录截图如下:

解密前工程目录截图如下:

解密后工程目录截图如下:

代码实现

代码结构如下:

  • main.go
package main

import (
    "awesomeProject5/common"
    "encoding/hex"
    "fmt"
    "io/ioutil"
    "strings"
)

func main() {
    key := "1234"
    files, err := common.WalkDir("C:\\Users\\admin\\Desktop\\新建文件夹", "")
    if err != nil {
        fmt.Println("Error:", err.Error())
    }
    for _, onefile := range files {
        log_filename := onefile + ".dec"
        content, err := ioutil.ReadFile(onefile)
        if err != nil {
            fmt.Println("Error reading file:", err)
            return
        }
        data := string(content)

        data = strings.ReplaceAll(data, "\n0x", "0x")
        datas := strings.Split(data, "\n")

        lable := strings.Split(datas[0], "_")[0]

        for _, str := range datas {
            if str == "" {
                break
            }
            buf := strings.ReplaceAll(strings.Split(strings.Split(str, " };")[0], "*/0x")[1], ", 0x", "")
            hex_buf, _ := hex.DecodeString(buf)
            data1 := hex_buf[4:]
            if common.BytesToInt_Little(hex_buf[:4]) == len(data1) {
                if strings.HasPrefix(str, lable) {
                    fmt.Println("************send************")
                    common.Writefile_A(log_filename, "************send************\r\n")
                    decryptd_text := common.Decrypt(data1, key)
                    if strings.HasPrefix(hex.EncodeToString(decryptd_text), "4d5a") {
                        common.Writefile(common.Searchpluginsname(common.CalculateMD5(string(decryptd_text)))+"_"+common.CalculateMD5(string(decryptd_text)), string(decryptd_text))
                        fmt.Println("保存文件至当前目录:", common.Searchpluginsname(common.CalculateMD5(string(decryptd_text)))+"_"+common.CalculateMD5(string(decryptd_text)))
                        common.Writefile_A(log_filename, "保存文件至当前目录:"+common.Searchpluginsname(common.CalculateMD5(string(decryptd_text)))+"_"+common.CalculateMD5(string(decryptd_text))+"\r\n")
                    } else {
                        fmt.Println("原始16进制数据:", hex.EncodeToString(hex_buf))
                        common.Writefile_A(log_filename, "原始16进制数据:"+hex.EncodeToString(hex_buf)+"\r\n")
                        fmt.Println("解密后16进制数据:", hex.EncodeToString(decryptd_text))
                        common.Writefile_A(log_filename, "解密后16进制数据:"+hex.EncodeToString(decryptd_text)+"\r\n")
                        fmt.Println("解密后字符串数据:", string(decryptd_text))
                        common.Writefile_A(log_filename, "解密后字符串数据:"+string(decryptd_text)+"\r\n")
                    }
                } else if strings.HasPrefix(str, "char peer") {
                    fmt.Println("************recv************")
                    common.Writefile_A(log_filename, "************recv************\r\n")
                    decryptd_text := common.Decrypt(data1, key)
                    fmt.Println("原始16进制数据:", hex.EncodeToString(hex_buf))
                    common.Writefile_A(log_filename, "原始16进制数据:"+hex.EncodeToString(hex_buf)+"\r\n")
                    fmt.Println("解密后16进制数据:", hex.EncodeToString(decryptd_text))
                    common.Writefile_A(log_filename, "解密后16进制数据:"+hex.EncodeToString(decryptd_text)+"\r\n")
                    fmt.Println("解密后字符串数据:", string(decryptd_text))
                    common.Writefile_A(log_filename, "解密后字符串数据:"+string(decryptd_text)+"\r\n")
                }
            }
        }
    }
}
  • xenorat.go
package common

import (
    "encoding/hex"
    "fmt"
    "os"
    "strings"
    "syscall"
    "unsafe"
)

type Header struct {
    Compressed       bool
    OriginalFileSize int
    T_offset         int
}

func (h *Header) ParseHeader(data []byte) {
    h.Compressed = false
    h.T_offset = 1
    if data[0] == byte(1) {
        h.Compressed = true
        h.OriginalFileSize = BytesToInt_Little(data[1:5])
        h.T_offset = 5
    } else if data[0] != byte(0) {
        os.Exit(1)
    }
}

func BTruncate(data []byte, offset int) (T_data []byte) {
    T_data = append(T_data, data[offset:]...)
    return
}

func Decrypt(data []byte, key string) (decryptd_text []byte) {
    key_sha256 := HashData_sha256([]byte(key))
    //aes_key, _ := hex.DecodeString("03AC674216F3E15C761EE1A5E255F067953623C8B388B4459E13F978D7C846F4")
    aes_key, _ := hex.DecodeString(key_sha256)
    aes_iv, _ := hex.DecodeString("00000000000000000000000000000000")

    var header Header
    if data[0] == byte(3) {
        data1 := BTruncate(data, 1)
        data2, _ := Aes_z_Decrypt(data1, aes_key, aes_iv)
        if data2[0] == byte(2) {
            decryptd_text = data2
            return
        }
        header.ParseHeader(data2)
        data3 := BTruncate(data2, header.T_offset)
        if header.Compressed {
            decryptd_text = append(decryptd_text, decompress(data3, header.OriginalFileSize)...)
        } else {
            decryptd_text = append(decryptd_text, data3...)
        }
        return
    } else if data[0] == byte(2) {
        fmt.Println("flag 2", hex.EncodeToString(data))
        return
    }
    header.ParseHeader(data)
    data4 := BTruncate(data, header.T_offset)
    if header.Compressed {
        decryptd_text, _ = Aes_z_Decrypt(decompress(data4, header.OriginalFileSize), aes_key, aes_iv)
    } else {
        decryptd_text, _ = Aes_z_Decrypt(data4, aes_key, aes_iv)
    }
    return
}

func decompress(data []byte, decryptd_text_len int) (decryptd_text []byte) {
    // 加载 ntdll.dll
    ntdll, err := syscall.LoadDLL("ntdll.dll")
    if err != nil {
        fmt.Println("无法加载 ntdll.dll:", err)
        return
    }

    // 获取 RtlDecompressBuffer 函数地址
    rtlDecompressBuffer, err := ntdll.FindProc("RtlDecompressBuffer")
    if err != nil {
        fmt.Println("无法找到 RtlDecompressBuffer 函数:", err)
        return
    }

    // 定义 RtlDecompressBuffer 函数的类型
    type RtlDecompressBufferFunc func(
        compressionFormat uint16,
        uncompressedBuffer uintptr,
        uncompressedBufferSize uint32,
        compressedBuffer uintptr,
        compressedBufferSize uint32,
        finalUncompressedSize *uint32,
    ) (status int32)

    // 将 Proc 转换为 Go 函数类型
    var rtlDecompressBufferTyped RtlDecompressBufferFunc
    rtlDecompressBufferTyped = func(
        compressionFormat uint16,
        uncompressedBuffer uintptr,
        uncompressedBufferSize uint32,
        compressedBuffer uintptr,
        compressedBufferSize uint32,
        finalUncompressedSize *uint32,
    ) (status int32) {
        ret, _, _ := rtlDecompressBuffer.Call(
            uintptr(compressionFormat),
            uncompressedBuffer,
            uintptr(uncompressedBufferSize),
            compressedBuffer,
            uintptr(compressedBufferSize),
            uintptr(unsafe.Pointer(finalUncompressedSize)),
        )
        return int32(ret)
    }

    // 准备输入数据
    compressionFormat := uint16(2) // 压缩格式,2 表示 LZNT1 压缩格式
    compressedData := data
    uncompressedBufferSize := uint32(decryptd_text_len) // 你可以根据需要调整大小
    uncompressedBuffer := make([]byte, uncompressedBufferSize)
    var finalUncompressedSize uint32

    // 调用 RtlDecompressBuffer 函数
    status := rtlDecompressBufferTyped(
        compressionFormat,
        uintptr(unsafe.Pointer(&uncompressedBuffer[0])),
        uncompressedBufferSize,
        uintptr(unsafe.Pointer(&compressedData[0])),
        uint32(len(compressedData)),
        &finalUncompressedSize,
    )

    // 检查状态并输出结果
    if status == 0 {
        decryptd_text = append(decryptd_text, uncompressedBuffer...)
    } else {
        fmt.Println("RtlDecompressBuffer解压失败,状态码:", status)
    }
    return
}

func Searchpluginsname(md5 string) (filename string) {
    switch strings.ToUpper(md5) {
    case "039D1F0DC21CD752CAC608434B205E3C":
        filename = "ReverseProxy.dll"
    case "E402280A434814FD9EECB5077B8AAC62":
        filename = "ScreenControl.dll"
    case "5E3E4438045C426410122210759CC0C8":
        filename = "Shell.dll"
    case "D66568E2FD174B6CC4F5AA10D9AB9EC6":
        filename = "Startup.dll"
    case "B1ACA76EE8C1D3FEC6EDD3A31F9728D8":
        filename = "SystemPower.dll"
    case "D1D9AEF0ED8093FF1ED157BB4AF3652C":
        filename = "Uacbypass.dll"
    case "C554DDB5DB3AC1539D9AB7DE049B8486":
        filename = "WebCam.dll"
    case "0E5695B84313F7ED7B86DBBA80B0342A":
        filename = "Chat.dll"
    case "0746F23B790FD439980D155A75E6275B":
        filename = "File manager.dll"
    case "B7C76350514A33374F4597D219D9FEC1":
        filename = "Fun.dll"
    case "5353C15BEE04DCAC54F80DD7D5660B21":
        filename = "Hvnc.dll"
    case "86BF07899C4E9764B0752713FE6F12C9":
        filename = "InfoGrab.dll"
    case "1E8FF2A86962488515380C6A27A775C3":
        filename = "KeyLogger.dll"
    case "4CF2A22B5FEFA18E2AB36A1D73F79833":
        filename = "KeyLoggerOffline.dll"
    case "AFB916213F3419BE21A061B782793C6E":
        filename = "LiveMicrophone.dll"
    case "4DAC21B4F2984931B9710CA50329023A":
        filename = "ProcessManager.dll"
    case "70BFB60E65F7FBF9FBCEE5C8AAA3FCE7":
        filename = "Registry Manager.dll"
    default:
        filename = ""
    }
    return
}
  • common.go
package common

import (
    "bytes"
    "crypto/aes"
    "crypto/cipher"
    "crypto/md5"
    "crypto/sha256"
    "encoding/binary"
    "encoding/hex"
    "fmt"
    "io"
    "os"
    "path/filepath"
    "strings"
)

func WalkDir(dirPth, suffix string) (files []string, err error) {
    files = make([]string, 0, 30)
    suffix = strings.ToUpper(suffix) //忽略后缀匹配的大小写

    err = filepath.Walk(dirPth, func(filename string, fi os.FileInfo, err error) error { //遍历目录
        if fi.IsDir() {
            return nil
        }

        if strings.HasSuffix(strings.ToUpper(fi.Name()), suffix) {
            files = append(files, filename)
        }

        return nil
    })

    return files, err
}

func checkPathIsExist(filename string) bool {
    var exist = true
    if _, err := os.Stat(filename); os.IsNotExist(err) {
        exist = false
    }
    return exist
}

func Writefile(filename string, buffer string) {
    var f *os.File
    var err1 error

    if checkPathIsExist(filename) { //如果文件存在
        f, err1 = os.OpenFile(filename, os.O_CREATE, 0666) //打开文件
    } else {
        f, err1 = os.Create(filename) //创建文件
    }
    _, err1 = io.WriteString(f, buffer)
    if err1 != nil {
        fmt.Println("写文件失败", err1)
        return
    }
    _ = f.Close()
}

func Writefile_A(filename string, buffer string) {
    var f *os.File
    var err1 error

    if checkPathIsExist(filename) { //如果文件存在
        f, err1 = os.OpenFile(filename, os.O_APPEND, 0666) //打开文件
    } else {
        f, err1 = os.Create(filename) //创建文件
    }
    _, err1 = io.WriteString(f, buffer)
    if err1 != nil {
        fmt.Println("写文件失败", err1)
        return
    }
    _ = f.Close()
}

func BytesToInt_Little(bys []byte) int {
    bytebuff := bytes.NewBuffer(bys)
    var data int32
    binary.Read(bytebuff, binary.LittleEndian, &data)
    return int(data)
}

func CalculateMD5(input string) string {
    hasher := md5.New()
    hasher.Write([]byte(input))
    md5Hash := hex.EncodeToString(hasher.Sum(nil))
    return md5Hash
}

func HashData_sha256(data []byte) string {
    hash := sha256.New()

    hash.Write(data)
    hashValue := hash.Sum(nil)

    hashString := hex.EncodeToString(hashValue)
    return hashString
}

func Aes_z_Decrypt(input, key, iv []byte) (output, newiv []byte) {
    output, err := aes_decrypt_cbc(input, key, iv)
    if err != nil {
        panic(err)
    }
    newiv = append(newiv, input[len(input)-16:]...)
    return
}

func aes_decrypt_cbc(data []byte, key []byte, iv []byte) ([]byte, error) {
    data_new := []byte{}
    data_new = append(data_new, iv...)
    data_new = append(data_new, data...)

    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }

    if len(data_new) < aes.BlockSize {
        return nil, fmt.Errorf("加密数据太短")
    }

    iv = data_new[:aes.BlockSize]
    data_new = data_new[aes.BlockSize:]

    if len(data_new)%aes.BlockSize != 0 {
        return nil, fmt.Errorf("加密数据长度不是块大小的整数倍")
    }

    mode := cipher.NewCBCDecrypter(block, iv)
    mode.CryptBlocks(data_new, data_new)

    data_new = pkcs5UnPadding(data_new)

    return data_new, nil
}

func pkcs5UnPadding(ciphertext []byte) []byte {
    length := len(ciphertext)
    unpadding := int(ciphertext[length-1])
    return ciphertext[:(length - unpadding)]
}
点击收藏 | 0 关注 | 1 打赏
  • 动动手指,沙发就是你的了!
登录 后跟帖