半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

admin 2024年7月3日14:19:29评论2 views字数 10940阅读36分28秒阅读模式
文章首发地址:
https://xz.aliyun.com/t/14979
文章首发作者:
T0daySeeker

概述

在上一篇《AgentTesla最新变体剖析-通过Web Panel方式窃取终端隐私数据》文章中,笔者对AgentTesla的最新变体样本进行了详细的剖析,由于当时笔者捕获的样本均是基于Web Panel方式窃取终端隐私数据的,而且基于代码以及网络中的介绍,AgentTesla样本应该还有其他通信方式,因此,笔者就还想再「扩线找找有没有使用SMTP或者FTP方式窃取终端隐私数据的」

由于AgentTesla远控是目前比较流行的远控木马之一,因此,基于简单的扩线方法,即可很快扩线一大批AgentTesla远控木马。

起初,笔者拿到扩线的AgentTesla远控木马时,还是基于上一篇文章的分析思路进行的一步一步分析,几个样本还好,但是当样本量突然上去后,笔者发现,这个工作不好干啊。。。重复工作。。。无意义工作。。。

因此,基于上述分析样本过程中的矛盾点,笔者就在琢磨,「如何能够快速的对这一批样本进行剖析,并且提取我所需要的内容呢?」

为了实现这个目标,笔者就开始对分析过程中的各个环节进行琢磨:

  • 分析难点:
    • 如何提取解密后的PE文件?「能否在进程替换前提取最终木马载荷」
  • 关键功能信息:
    • 配置信息
    • download功能的下载地址?
    • 窃取数据的方法?Web Panel、SMTP、FTP

基于上述思路,笔者尝试编写了多个脚本用以辅助半自动化的对AgentTesla最新变体样本开展分析工作,通过努力,最终实现对扩线样本进行了批量剖析,获得大量SMTP、FTP账号信息。

内存提取PE文件

为了能够完全提取解密后的PE文件,笔者也是尝试了多个思路:

  • 思路一:基于上一篇文章中提到的解密算法对相关加密载荷进行解密

    • 实际遇到的问题1:在SimpleLogin.dll解密Tyrone.dll样本的过程中,无法很好的动态调试其解密算法,而且其解密过程涉及了多个样本文件中的代码,因此很容易触发异常。
    • 实际遇到的问题2:解密算法均需要解密密钥,若解密密钥不同,就算成功复现了解密算法,还是需要再次通过动态调试才能提取解密密钥,因此也没有减少工作量。
  • 思路二:在调试过程中,提取进程内存中的所有PE文件

    • 实际遇到的问题1:每个木马进程内存中均能提取很多PE文件,而且大部分PE文件还是正常的dll模块。
    • 实际遇到的问题2:从木马进程内存中提取的PE文件,存在PE文件重复的情况。
    • 实际遇到的问题3:从木马进程内存中提取的PE文件,存在PE文件头是正常PE文件载荷,PE文件头后的内容并非为实际PE文件载荷。
  • 思路三:由于木马进程会调用VirtualAllocEx、WriteProcessMemory等函数以实现进程替换,因此,我们可通过OD对WriteProcessMemory函数下断点,成功断下来后,即可根据API查看待写入的PE文件载荷内容。

通过对比,笔者最终采用了思路三对AgentTesla最新变体运行过程中的最终载荷进行提取。

提取内存片段

由于AgentTesla最新变体在运行过程中,会通过进程替换技术将解密后的最终载荷写入新进程的内存空间中,因此,我们可通过如下方式尝试提取内存片段:

  • 使用OD调试AgentTesla最新变体程序,尝试下WriteProcessMemory函数断点;
  • 运行,即可在WriteProcessMemory函数断点处中断;
  • 查看WriteProcessMemory函数的lpBuffer参数(指向要写入数据的缓冲区的指针),发现其数据为PE文件载荷;
  • 由于lpBuffer参数对应内存地址为内存块中的一部分,「因此,若尝试直接提取内存数据,然后再根据PE文件结构提取PE文件数据还是比较麻烦,而且此内存块中还存在其他PE文件;」
  • 尝试直接提取lpBuffer参数对应内存地址的内存块;

相关截图如下:

半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

从内存片段中还原PE文件

成功提取携带AgentTesla最终载荷的内存块后,我们即可尝试从内存片段中还原PE文件,为有效提取还原PE文件,笔者尝试以如下逻辑简单编写了一个脚本程序:

  • 在内存块中搜索PE文件结构中的"This program cannot be run in DOS mode"字符串;
  • 根据字符串偏移,查找PE文件头;
  • 根据PE文件头,计算PE文件大小;
  • 根据载荷偏移,计算PE文件载荷的Hash值;「(简单去重,避免还原出来的PE文件为相同PE文件)」
  • 根据载荷偏移,判断PE文件载荷末尾是否是以00结尾;「(简单区分,避免PE文件头以后的数据为非实际PE文件数据)」
  • 若Hash值为首次计算所得,则从内存块中提取PE文件;

代码效果如下:

半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

解密后的载荷属性信息截图如下:「(最终载荷的属性名称均是形如bfb5da9f-48ba-40d7-85d4-7ec204e8e6d3.exe结构)」

半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

代码实现如下:

package main

import (
 "crypto/sha256"
 "debug/pe"
 "encoding/hex"
 "fmt"
 "io"
 "io/ioutil"
 "os"
 "path/filepath"
 "regexp"
 "strconv"
 "strings"
)

func main() {
 hashs := []string{}

 files, err := WalkDir("C:\Users\admin\Desktop\新建文件夹", "")
 if err != nil {
  fmt.Println("Error:", err.Error())
 }
 for _, onefile := range files {
  SearchPE(onefile, &hashs)
 }
}

func SearchPE(file_in string, hashs *[]string) {
 output_dir := "./output/"
 _, fileName := filepath.Split(file_in)
 // 读取文件的所有内容
 data, err := ioutil.ReadFile(file_in)
 if err != nil {
  fmt.Println("Error reading file:", err)
  return
 }
 reg := regexp.MustCompile("This program cannot be run in DOS mode")
 offsets := reg.FindAllIndex(data, -1)
 //fmt.Println(offsets)
 for _, offset := range offsets {
  buffer := []byte{}
  buffer = append(buffer, data[offset[0]-0x4e:]...)
  Writefile("tmp", string(buffer))
  size := getfilesize("tmp")
  buffer1 := []byte{}
  buffer1 = append(buffer1, buffer[:size]...)
  fmt.Println("offset PE:0x" + strconv.FormatInt(int64(offset[0]-0x4e), 16))

  hash := HashData_sha256(buffer1)
  //部分提取的文件其实只有PE头是正常的,因此,通过判断末尾数据来筛选
  if !ContainsAny(hash, *hashs) && strings.HasSuffix(hex.EncodeToString(buffer1), "00000000000000000000000000000000") {
   *hashs = append(*hashs, hash)
   Writefile(output_dir+fileName+"offset_0x"+strconv.FormatInt(int64(offset[0]-0x4e), 16), string(buffer1))
  }
  os.Remove("tmp")
 }
}

func getfilesize(filePath string) (size int) {
 file, err := os.Open(filePath)
 if err != nil {
  fmt.Printf("Error opening file: %vn", err)
  return
 }
 defer file.Close()

 peFile, err := pe.NewFile(file)
 if err != nil {
  fmt.Printf("Error parsing PE file: %vn", err)
  return
 }
 defer peFile.Close()

 fmt.Printf("RawSize: 0x%Xn", peFile.Sections[len(peFile.Sections)-1].Size)
 fmt.Printf("RawAddress: 0x%Xn", peFile.Sections[len(peFile.Sections)-1].Offset)

 aa := peFile.Sections[len(peFile.Sections)-1].Size + peFile.Sections[len(peFile.Sections)-1].Offset
 size = int(aa)
 return
}

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 HashData_sha256(data []byte) string {
 // 创建 SHA256 哈希函数
 hash := sha256.New()

 // 将字符串转换为字节数组,并计算哈希值
 hash.Write(data)
 hashValue := hash.Sum(nil)

 // 将哈希值转换为十六进制字符串
 hashString := hex.EncodeToString(hashValue)
 return hashString
}

func ContainsAny(str string, elements []string) bool {
 for element := range elements {
  e := elements[element]
  if strings.Contains(e, str) {
   return true
  }
 }
 return false
}

自动化提取配置信息

为了能够完全提取配置信息,笔者也是尝试了多个思路:

  • 思路一:尝试基于配置信息的16进制特征对AgentTesla最终载荷样本的配置信息进行提取
    • 实际遇到的问题:基于16进制只能提取配置信息的变量值,具体变量名无法很好的对应
  • 思路二:尝试对反编译后的AgentTesla最终载荷样本的代码进行分析,提取反编译后的配置信息
    • 实际遇到的问题:最开始没有找到很合适的命令行反编译工具

通过对比,笔者最终采用了思路二对AgentTesla最新变体运行过程中的最终载荷的配置信息进行提取。

思路一的部分截图如下:

半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

批量反编译NET样本

为了实现批量反编译NET样本,笔者尝试对多款NET反编译工具进行了研究,梳理发现:

  • dnspy:不支持命令行运行;
  • ILSpy存在一个命令行版本ilspycmd,支持命令行运行;

安装ilspycmd工具的流程如下:

  • 安装 .NET SDK 6.0:笔者安装的版本为dotnet-sdk-6.0.423-win-x64.exe;
  • 安装ilspycmd工具:dotnet tool install --global ilspycmd
  • 验证ilspycmd安装:ilspycmd --help(如果安装非.NET SDK 6.0版本,则会报错)
  • 使用ilspycmd反编译程序集:ilspycmd -o "./output" "assembly.dll"

相关操作截图如下:「(反编译后,会生成.decompiled.cs后缀文件)」

半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

配置信息结构对比

尝试对AgentTesla最新变体运行过程中的最终载荷的配置信息进行对比,发现其配置信息项的顺序以及名称均基本相同。

相关截图如下:

半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

自动化提取配置信息

因此,基于上述配置对比信息,笔者尝试基于如下思路构建自动化提取配置信息的脚本程序:

  • 遍历AgentTesla最新变体运行过程中的最终载荷;
  • 使用ilspycmd工具对样本进行批量反编译;
  • 使用正则匹配反编译.decompiled.cs文件中的URL信息;
  • 使用正则匹配反编译.decompiled.cs文件中的配置信息;

代码效果如下:

半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

代码实现如下:

package main

import (
 "fmt"
 "io/ioutil"
 "os"
 "os/exec"
 "path/filepath"
 "regexp"
 "strings"
)

func main() {
 fmt.Println("需安装ilspycmd工具")
 fmt.Println()
 files, err := WalkDir("C:\Users\admin\Desktop\test", "")
 if err != nil {
  fmt.Println("Error:", err.Error())
 }
 for _, onefile := range files {
  fileName := filepath.Base(onefile)
  fileExt := filepath.Ext(onefile)
  decompiledfile := strings.Split(fileName, fileExt)[0] + ".decompiled.cs"
  if !CheckPathIsExist("./output/" + decompiledfile) {
   CmdRun(`C:\Users\admin\.dotnet\tools\ilspycmd.exe -o ./output/ ` + onefile)
  }
  if CheckPathIsExist("./output/" + decompiledfile) {
   fmt.Println("**********" + fileName + " URL**********")
   SearchURLs("./output/" + decompiledfile)
   fmt.Println("**********" + fileName + " Config**********")
   SearchConfig("./output/" + decompiledfile)
   fmt.Println()
  }
 }
}

func SearchURLs(onefile string) {
 // 读取文本文件内容
 content, err := ioutil.ReadFile(onefile) // 请替换为实际的文件路径
 if err != nil {
  fmt.Printf("无法读取文件:%vn", err)
  return
 }

 // 定义正则表达式
 re := regexp.MustCompile(`(http://|https://)[^s]+`)

 // 在文本中查找匹配的字符串
 matches := re.FindAllString(string(content), -1)

 // 输出匹配到的字符串
 for _, match := range matches {
  if strings.Contains(match, `"`) {
   fmt.Println(strings.Split(match, `"`)[0])
  } else {
   fmt.Println(match)
  }
 }
}

func SearchConfig(onefile string) {
 // 读取文本文件内容
 content, err := ioutil.ReadFile(onefile) // 请替换为实际的文件路径
 if err != nil {
  fmt.Println(err.Error())
 }

 // 定义正则表达式,用于匹配两个字符串之间的内容
 re := regexp.MustCompile(`(?s)publics+statics+strings+PcHwid(.*?)publics+statics+strings+StartupRegName`)

 // 查找匹配的字符串
 matches := re.FindStringSubmatch(string(content))

 if len(matches) > 1 {
  // 输出匹配到的字符串(第一个子匹配项)
  //fmt.Println("匹配到的内容:", matches[0])
  output := matches[0]
  output = strings.ReplaceAll(output, "t", "")
  output = strings.ReplaceAll(output, "public static string ", "")
  output = strings.ReplaceAll(output, "public static bool ", "")
  output = strings.ReplaceAll(output, "public static int ", "")
  output = strings.ReplaceAll(output, "Convert.ToBoolean(", "")
  output = strings.ReplaceAll(output, "Convert.ToInt32(", "")
  output = strings.ReplaceAll(output, ");", "")
  output = strings.ReplaceAll(output, ";", "")
  output = strings.ReplaceAll(output, "rnrn", "rn")
  fmt.Print(strings.Split(output, "StartupRegName")[0])
 } else {
  fmt.Println("未找到匹配的内容")
 }

 re1 := regexp.MustCompile(`publics+statics+strings+StartupRegName.*`)

 // 查找匹配的字符串
 matches1 := re1.FindStringSubmatch(string(content))

 if len(matches1) > 0 {
  output := matches1[0]
  output = strings.ReplaceAll(output, "public static string ", "")
  output = strings.ReplaceAll(output, ";r", "")
  fmt.Println(output)
 } else {
  fmt.Println("未找到匹配的内容")
 }

}

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 CmdRun(command string) {
 parts := strings.Fields(command)
 head := parts[0]
 parts = parts[1:]
 cmd := exec.Command(head, parts...)
 output, err := cmd.CombinedOutput()
 if err != nil {
  fmt.Println(err.Error())
  fmt.Println(string(output))
 } else {
  fmt.Println(string(output))
 }
}

多阶段部分解密算法复现

虽然笔者最终未通过复现多阶段解密算法的方式去解密各阶段中的PE文件,但笔者也尝试编写了部分脚本用以做解密尝试,因此,笔者也将其解密脚本放置于文章中。

解密SimpleLogin.dll

代码效果如下:

半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

代码实现如下:

package main

import (
 "fmt"
 "io"
 "io/ioutil"
 "os"
)

func main() {
 file_in := "C:\Users\admin\Desktop\新建文件夹\off"
    
 array, err := ioutil.ReadFile(file_in)
 if err != nil {
  fmt.Println("Error reading file:", err)
  return
 }
 num := len(array)
 first := "J9EZ6H5428445"
 second := "C755C8RZH"
 first_second := first + second
 for i, data := range array {
  num2 := i % 22
  b := first_second[num2]
  num3 := (i + 1) % num
  num4 := int(data ^ b)
  num5 := num4 - int(array[num3]) + 256
  array[i] = byte(num5 & 255)
 }
 Writefile(file_in+"_dec_SimpleLogin.dll", string(array))
}

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()
}

解密Gamma.dll

直接使用压缩软件即可实现解密Gamma.dll文件。

相关截图如下:

半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

半自动化批量剖析的最终实现效果--获取大量SMTP、FTP账号信息

在文章开头,笔者就说笔者通过扩线获取了大量的AgentTesla最新变体样本,因此,基于上述半自动化分析手法,笔者可很快速对上述样本的功能行为进行梳理提取。

通过梳理,笔者发现了大量的SMTP、FTP账号信息,相关截图如下:

半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息

原文始发于微信公众号(T0daySeeker):半自动化批量剖析AgentTesla最新变体的方法探究--最终获取大量SMTP、FTP账号信息

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月3日14:19:29
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   半自动化批量剖析AgentTesla最新变体的方法探究最终获取大量SMTP、FTP账号信息https://cn-sec.com/archives/2913565.html

发表评论

匿名网友 填写信息